Usage patterns and gotchas — @vueuse/integrations 15.0.0
useAxios
One-off request; the return is thenable:
const { data, error } = await useAxios('/api/posts')Manual trigger with abort of the in-flight request (default abortPrevious: true):
const { execute, abort, isLoading } = useAxios('/api/posts', {}, { immediate: false })
async function refresh() {
await execute() // same url
await execute('/api/other') // new url
}Shared config through an axios instance:
const api = axios.create({ baseURL: 'https://api.example.com' })
const { data } = useAxios<User[]>('/users', api, { initialData: [] })resetOnExecute: trueresetsdatatoinitialDatabefore each execution (dist/useAxios.js:59-61).- Without
initialData,datastaysundefineduntil success; guard renders onisFinished/isLoading. - Do not pass
immediate: trueto a no-url call;executeerrors withERR_INVALID_URLwhen no url is resolvable (dist/useAxios.js:73-77).
useIDBKeyval
Object form (v15):
const { data: settings, isFinished, set } = useIDBKeyval('settings', { theme: 'dark' })
// reactive writes; deep watch persists nested mutations
settings.value.theme = 'light'
// await an explicit write
await set({ theme: 'system' })- Disable cross-tab sync for private values:
useIDBKeyval('key', 0, { listenToStorageChanges: false })(dist/useIDBKeyval.js:60). - Custom codec for dates:
useIDBKeyval<Date>('seen', new Date(), {
serializer: {
read: raw => new Date(raw as string),
write: value => value.toISOString(),
},
})writeDefaults: falseskips persisting the initial value for absent keys (dist/useIDBKeyval.js:26-29).- Large immutable data:
shallow: trueavoids deep watching; useset()for writes.
useSortable
List follows the DOM automatically; keep the list as a plain ref of objects keyed by id:
const list = ref([{ id: 1 }, { id: 2 }])
useSortable(listRef, list, {
handle: '.handle',
animation: 150,
})Conditional rendering needs watchElement: true (added in v14.2.0, https://github.com/vueuse/vueuse/issues/5189):
useSortable(el, list, { watchElement: true })Custom onUpdate (replaces the default array move):
import { moveArrayElement } from '@vueuse/integrations/useSortable'
useSortable(el, list, {
onUpdate: (e) => {
moveArrayElement(list, e.oldIndex, e.newIndex, e)
nextTick(() => {
// array and DOM are settled; run post-move logic here
})
},
})moveArrayElementreorders the array insidenextTick; read the moved array in anextTickcallback, not inline (dist/useSortable.js:84-90).- Pausing interaction without destroying:
option('disabled', true);stop()destroys the instance,start()recreates it from the current element. - String selectors never re-resolve after
stop(); element refs do whenwatchElementis on.
useFocusTrap
Modal with v-if:
const target = useTemplateRef<HTMLElement>('modal')
const { activate, deactivate } = useFocusTrap(target)
async function open() {
show.value = true
await nextTick() // trap created by the post-flush watcher once the element exists
activate()
}Or let the component handle mount and cleanup:
<script setup>
import { UseFocusTrap } from '@vueuse/integrations/useFocusTrap/component'
</script>
<template>
<UseFocusTrap v-if="show" :options="{ immediate: true }">
<div class="modal" tabindex="-1">...</div>
</UseFocusTrap>
</template>- Do not call
activate()before the element renders; the trap does not exist yet (dist/useFocusTrap.js:39). deactivate()runs automatically on scope dispose (dist/useFocusTrap.js:58).- User-triggered escape/back-compat keys belong in focus-trap
Options(escapeDeactivates,allowOutsideClick), not in wrapper code.
useCookies
Reactive reads with an explicit dependency list:
const { get } = useCookies(['token'])
const token = computed(() => get('token'))Let get grow the watch list itself:
const { get } = useCookies(['token'], { autoUpdateDependencies: true })
// get('theme') now also triggers updates when 'theme' changesSSR (Node request headers):
// server plugin / entry
const cookies = createCookies(req)
// component setup
const { get } = cookies(['session'])getreactivity works through a change listener plus atouchescounter; it only updates when a watched cookie actually changes value (dist/useCookies.js:35-39,75-79).- In Nuxt, import
useCookiesexplicitly; Nuxt auto-imports its ownuseCookie(singular) which is a different API. - Write options:
set(name, value, { maxAge, path, secure, sameSite, expires })— same shape as universal-cookie.
useFuse
const search = ref('')
const { results } = useFuse(search, items, {
fuseOptions: { keys: ['title', 'author.name'] },
resultLimit: 10,
matchAllWhenSearchEmpty: true, // show all while the box is empty
})- Results are
FuseResult[]: readresult.itemfor the datum,result.refIndexfor its position in the source array (dist/useFuse.js:22-30). - Changing
fuseOptionsreactively rebuilds the index; changingdatareuses it viasetCollection.
useChangeCase
const input = ref('hello world')
const camel = useChangeCase(input, 'camelCase') // 'helloWorld'
camel.value = 'foo bar' // input becomes 'foo bar'
// read-only over a getter
const slug = useChangeCase(() => title.value, 'paramCase')- Switch
typereactively:useChangeCase(input, computed(() => kind.value))(dist/useChangeCase.js:16-20). - Invalid types throw at read time, so validate user-supplied type strings first.
useDrauu
const svg = useTemplateRef<SVGSVGElement>('pad')
const { brush, undo, redo, clear, canUndo, canRedo, dump, load, onCommitted } = useDrauu(svg, {
brush: { color: 'red', size: 5 },
})
brush.value.mode = 'line' // reactive; syncs to the instance
brush.value.color = '#0f0'
onCommitted(({ node }) => saveSnapshot(dump()))- Target any element other than
<svg>and no instance is created (dist/useDrauu.js:79-80). dump()/load()exchange SVG strings; persist them elsewhere.
useJwt
const token = ref<string>()
const { payload } = useJwt(token, {
fallbackValue: { sub: '' } as JwtPayload,
onError: err => console.warn('bad token', err),
})payload/header recompute whenever the token string changes; expired or malformed tokens yield fallbackValue, they do not throw.
useNProgress
const { isLoading, progress, remove } = useNProgress(0, { trickle: true })
watch(route, () => { isLoading.value = true })
watch(pageData, () => { isLoading.value = false })- Setting
isLoading = truecallsstart();falsecallsdone()(dist/useNProgress.js:12-15). progressacceptsnull(reset),0..1values, and syncs both directions with patchednprogress.set(dist/useNProgress.js:17-24).- One instance is enough per app;
nprogressis a singleton and concurrentuseNProgresscalls share its DOM bar.
useQRCode
const url = ref('https://vueuse.org')
const qr = useQRCode(url, { margin: 2, width: 256, color: { dark: '#000', light: '#fff' } })- The ref starts as
''; awaitwatchEffect/until(qr).toBe(...)or bind withv-if="qr"(dist/useQRCode.js:14-18). - Generation is async and client-only; render placeholder markup on the server.
useAsyncValidator
const form = reactive({ name: '', age: 18 })
const rules: Rules = {
name: { required: true, message: 'Name required' },
age: [{ type: 'number', min: 18 }, { type: 'integer', message: 'Integer only' }],
}
// automatic: revalidates on every form/rules change
const { pass, errors } = useAsyncValidator(form, rules)
// manual: run on submit only
const { execute } = useAsyncValidator(form, rules, { manual: true })
async function submit() {
const { pass, errorFields } = await execute()
if (pass) send()
}- Component form:
<UseAsyncValidator :form="form" :rules="rules" v-slot="{ pass, errors }">
<input v-model="form.name">
<span v-if="!pass">{{ errors?.[0]?.message }}</span>
</UseAsyncValidator>errorsdefaults to[]anderrorFieldsto{}; checkpassfirst (dist/useAsyncValidator.js:18-25).