How-to debounce values and functions in VueJS 3 using composable components

Tips & Tricks / Tutorials / Web Development

10 minutes
Wed Nov 17 2021
IPOCS

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.

Setup environment

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.

Scaffolding and starting your Vite/VueJs project

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

Implement simple reactive input

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.

Implement debounce composable function for variables

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.

Implement debounce composable function for methods

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.

Project sources

The source files for this sample project are available on GitHub.

Final words

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.