defineUseFetchAddon v4.6
defineUseFetchAddon defines a reusable extension for useFetch composables created with createUseFetch.
An addon can declare custom call-site options, wrap the handler with middleware, attach custom reactive logic or extend the returned object.
Usage
Pass addons to createUseFetch via the addons array.
This addon attaches custom logic: a watcher that refreshes the data whenever the window regains focus and gates it behind a custom refreshOnFocus option:
const refreshOnFocus = defineUseFetchAddon({
// augment the call-site options for the custom useFetch instance 👇
setup: (options: UseFetchAddonOptions<{ refreshOnFocus?: boolean }>) => {
// 👈 run code *before* calling `useAsyncData` in `useFetch`
if (import.meta.server || !options.refreshOnFocus) { return }
return (asyncData) => {
// 👈 run code *after* calling `useAsyncData` in `useFetch`
const focused = useWindowFocus()
watch(focused, (focused) => {
if (focused) { asyncData.refresh() }
})
return { focused } // 👈 extend the returned object with a new property
}
},
})
export const useCustomFetch = createUseFetch({ addons: [refreshOnFocus] })
<script setup lang="ts">
const { data } = await useCustomFetch('/some-endpoint', { refreshOnFocus: true })
</script>
The addon's setup function runs on every call of the composable, and receives the merged options (factory defaults plus caller options).
addons array, with middleware from the first addon as the outermost wrapper. Listing the same addon object more than once runs it only once.Custom Options
Declare custom options by annotating the setup options parameter with UseFetchAddonOptions<{ ... }>. The options become part of the created composable's signature, fully typed for callers:
const auth = defineUseFetchAddon({
setup: (options: UseFetchAddonOptions<{ auth?: MaybeRefOrGetter<boolean> }>) => {
const { token } = useTokenStore()
options.auth ??= true
options.onRequest.push(({ options: fetchOptions }) => {
if (!toValue(options.auth)) { return }
fetchOptions.headers.set('Authorization', `Bearer ${token.value}`)
})
},
})
export const useCustomFetch = createUseFetch({ addons: [auth] })
<script setup lang="ts">
// `auth` is typed
const { data } = await useCustomFetch('/public-endpoint', { auth: false })
</script>
Custom options stay in the merged options object, so they are also passed to $fetch along with the request options, which ignores keys it does not recognise.
Extending the Return Value
If setup returns a function, that function is called with the async data instance. Any object it returns is merged into the composable's return value:
const timestamps = defineUseFetchAddon({
setup: () => {
const refreshedAt = ref<Date>()
return (asyncData) => {
watch(asyncData.status, (status) => {
if (status === 'success') { refreshedAt.value = new Date() }
}, { immediate: true })
return { refreshedAt: readonly(refreshedAt) }
}
},
})
export const useCustomFetch = createUseFetch({ addons: [timestamps] })
<script setup lang="ts">
// `refreshedAt` is typed
const { data, refreshedAt } = await useCustomFetch('/modules')
</script>
Middleware
Middleware wraps the execution of the request handler. Call next() to continue the chain (and get the resolved data), or throw to abort. Middleware from the first addon is the outermost wrapper:
const minDuration = defineUseFetchAddon({
setup: (options: UseFetchAddonOptions<{ minDuration?: number }>) => {
options.middleware.push(async (next) => {
const [result] = await Promise.all([
next(),
new Promise(resolve => setTimeout(resolve, toValue(options.minDuration) ?? 300)),
])
return result
})
},
})
Contributing to the Auto-Generated Key
Custom options are not part of the auto-generated key by default. If a custom option affects the response, provide a key resolver so calls with different values do not share a cached entry. Return a serializable value, or undefined to contribute nothing:
const scoped = defineUseFetchAddon({
setup: (options: UseFetchAddonOptions<{ scope?: string }>) => {
options.scope ??= 'default'
// ...
},
key: options => toValue(options.scope),
})
setup before key. TypeScript infers the custom options from the setup parameter annotation, and a key resolver written before a setup that returns an extension function is checked first and typed with empty options.Wrapping then, catch and finally
As an advanced escape hatch, the extension object may include then, catch or finally functions. These are not merged into the instance. Instead they wrap the corresponding method of the awaitable promise, receiving the original method as their first argument.
addons array: the first addon's wrapper wraps all the following ones, just as its middleware would.Type
function defineUseFetchAddon<Opts extends Record<string, any> = {}, Ext = {}> (addon: {
setup: (options: UseFetchAddonOptions<Opts>) => ((asyncData: AsyncDataAddonInstance) => Ext | void) | void
key?: (options: UseFetchAddonOptions<Opts>) => SerializableValue
}): UseFetchAddon<Opts, Ext>
middleware, onRequest, onRequestError, onResponse and onResponseError are always normalized to arrays, so interceptors from the caller and from other addons are preserved.