With the introduction of Composition API, we got a new way of writing reactive logic, namely, ref
and reactive
methods. In this article, I want to show you how you can create a debounced ref that will update its value only after a specified delay. A denounced ref can be very useful, for example, if you have an input field with an autocomplete where an API request is made after the search query state changes.
Debouncing is a nice optimisation pattern, and without it, an API request would be made after every keystroke. Battering a server is not optimal, so let's get started.
Project Setup
To showcase this example, I have created a new project with Vite. If you would like to follow along, you can create one by running yarn create @vitejs/app debounced-ref --template vue
or npm init @vitejs/app debounced-ref --template vue
. You can also find the full code example in this GitHub repo.
App.vue
As I mentioned at the start, a debounced ref could be used to delay API requests when a user enters search criteria in an input field. Below you can see the setup for it.
<template>
<div>
<label :class="$style.label">Search query</label>
<input :class="$style.input" v-model="query" type="text" />
<div>Value: {{ query }}</div>
</div>
</template>
<script>
import { watch } from 'vue'
import useDebouncedRef from './composables/useDebouncedRef'
export default {
setup() {
const query = useDebouncedRef('', 400)
watch(query, newQuery => {
console.log({ newQuery })
// init an API request
})
return {
query,
}
},
}
</script>
<style module>
.label {
display: block;
}
.input {
margin-top: 5px;
margin-bottom: 20px;
}
</style>
In the template, we have a label
, input
, and div
that contains the query
value. When we're done, you will see that the value is updated only after a delay. In the setup
method, we create a debounced ref using useDebouncedRef
function and pass an empty string as an initial value as well as 400, which is the debounce delay. We will create it in a moment. Besides that, we also have a watcher that observes the query
ref. That's where you could initialise a function that would perform an API request.
useDebouncedRef.js
Here is the implementation of the useDebouncedRef
composable.
import { ref, customRef } from 'vue'
const debounce = (fn, delay = 0, immediate = false) => {
let timeout
return (...args) => {
if (immediate && !timeout) fn(...args)
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
const useDebouncedRef = (initialValue, delay, immediate) => {
const state = ref(initialValue)
const debouncedRef = customRef((track, trigger) => ({
get() {
track()
return state.value
},
set: debounce(
value => {
state.value = value
trigger()
},
delay,
immediate
),
}))
return debouncedRef
}
export default useDebouncedRef
In the useDebouncedRef.js file, we have debounce
and useDebouncedRef
functions. The debounce
function takes care of executing a callback function after the specified delay time passed. Besides a callback function and delay number, it also accepts a third parameter called immediate
. As the name suggests, it is used to indicate if the callback should be executed immediately. This little helper function could be abstracted into its own utility file and reused in other parts of your applications.
In the useDebouncedRef
composable, we declare a new reactive value using the ref
method imported from the vue
package. However, we also use a customRef
, and that's where the magic happens. The customRef
gives us explicit control over tracking dependencies and triggering state updates. The getter calls the track
method and returns the current state
value. For the setter, we assign a result of the debounce
function, which receives a callback as the first parameter and forwards delay
and immediate
arguments. In the callback, we update the state
ref and trigger the update of the customRef
. That's all. If you try to enter something in the input field, you should see, that the value text updates only after you stop typing for a specified amount of time.
I hope you enjoyed this article. If you would like to learn more tips, advanced patterns, techniques and best practices related to Vue, you might want to check out "Vue - The Road To Enterprise" book, sign up for the newsletter, and follow me on Twitter.