When developing web applications, or in software development in general, there is almost always the need to debounce a given value or function. What this means is that you want to make a function, input or event etc. to wait a certain amount of time before executing/evaluating.
This helps when you want to limit the number of times an action is triggered, for example when you implement search box suggestions, user inputs, or when clicking buttons to perform some expensive/time-consuming operations.
For this example we will be using VueJs 3
with the composition API + Vite
for the frontend tooling, though it should be fairly easy to implement the same debounce
functionalities in your prefered language/environment.
You need to have at least NodeJS version 12 to be able to run Vite, so make sure you install it before proceeding further.
There are many ways of scaffolding your new project, though we will be using npm
as in:
$ cd ~/Code/
$ npm init vite@latest
You will be prompted to setup your new project, we've chosen the following:
$ npm init vite@latest
✔ Project name: … ipocs-debounce-example
✔ Select a framework: › vue
✔ Select a variant: › vue
Once the project is created, use the following commands to install the dependencies and start the project in development mode:
$ cd ipocs-debounce-example
$ npm install
$ npm run dev
Open your new VueJs project using your favorite editor and adjust src/App.vue
component so it includes a simple text input, bound to a reactive variable to hold up our value:
<template>
<input
type="text"
name="fullName"
placeholder="Enter your full name"
v-model="myName"
/>
<pre>Input is: {{ myName }}</pre>
</template>
<script setup>
import { ref } from 'vue'
const myName = ref('')
</script>
In the above component, the input value is bound to a reactive myName
variable which updates instantly as we type into the input text field. While in most cases this is fine, sometimes we don't want to immediatelly update the value of the reactive variable and here's where the debounce
is what we need.
So, start by creating a new directory which will hold up our composable functions and create a blank useDebounce.js
file, for example:
$ mkdir src/composables
$ touch src/composables/useDebounce.js
Next, open the newly created useDebounce.js
file in your editor and add the following:
import { ref, customRef } from 'vue'
const myDebounce = (fn, delay = 0, immediate = false) => {
let timeout
return (...args) => {
if (immediate && !timeout) fn(...args)
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
const useDebounce = (initialValue, delay, immediate) => {
const state = ref(initialValue)
const debouncedReference = customRef(
(myTrack, myTrigger) => ({
get() {
myTrack()
return state.value
},
set: myDebounce(
value => {
state.value = value
myTrigger()
},
delay,
immediate
),
})
)
return debouncedReference
}
export {
useDebounce
}
Now, edit your src/App.vue
and use the useDebounce
composable to debounce the reactive input, as in:
<template>
<input
type="text"
name="fullName"
placeholder="Enter your full name"
v-model="myName"
/>
<pre>Input is: {{ myName }}</pre>
</template>
<script setup>
import { useDebounce } from './composables/useDebounce'
const myName = useDebounce('', 2000)
</script>
For the sake of this article, we've debounced the reactive myName
variable for 2 seconds.
Similar to the debounced reactive variable value, sometimes we need to debounce a given function/method. To achieve this, we need to implment similar logic which will handle this.
So, open the useDebounce.js
file for editing and implement the useDebounceFn
function:
import { ref, customRef } from 'vue'
const myDebounce = (fn, delay = 0, immediate = false) => {
let timeout
return (...args) => {
if (immediate && !timeout) fn(...args)
clearTimeout(timeout)
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
const useDebounce = (initialValue, delay, immediate) => {
const state = ref(initialValue)
const debouncedReference = customRef(
(myTrack, myTrigger) => ({
get() {
myTrack()
return state.value
},
set: myDebounce(
value => {
state.value = value
myTrigger()
},
delay,
immediate
),
})
)
return debouncedReference
}
const useDebounceFn = (fn, delay) => {
let timeout
return (...args) => {
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => {
fn(...args)
}, delay)
}
}
export {
useDebounce,
useDebounceFn
}
Now, go back to your src/App.vue
and utilize the useDebounceFn
as in:
<template>
<input
type="text"
name="fullName"
placeholder="Enter your full name"
v-model="myName"
:disabled="submitted"
/>
<pre>Input is: {{ myName }}</pre>
<button type="button"
@click="handleSubmit"
:disabled="!myName || submitted"
>
<span v-if="!submitted">SUBMIT NAME</span>
<span v-else>Loading...</span>
</button>
</template>
<script setup>
import { ref } from 'vue'
import {
useDebounce,
useDebounceFn
} from './composables/useDebounce'
const myName = useDebounce('', 600)
const submitted = ref(false)
const myDebouncedMethod = useDebounceFn(() => {
// just reset the myName and submitted values for testing
myName.value = ''
submitted.value = false
}, 2000)
const handleSubmit = () => {
submitted.value = true
myDebouncedMethod()
}
</script>
As you can see it in action, once you fill in the input value, it's been debounced by 600ms
and once you click the submit button, the handleSubmit
method is immediatelly called and the submitted
boolean is set to true
, but the myDebouncedMethod
is debounced for 2000ms
, which means it will not be immediatelly fired.
The source files for this sample project are available on GitHub.
Do you need a custom web application development or you need help from our web development team with your existing project? Please do not hesitate to contact us using our contact form and we'll get back to you shortly.