Back to articles

How to Debounce Input in Vue 3

January 7th, 2023

If you have (or want to implement) forms that autosave as a user types, there's a really important consideration to make when making requests to an API — the amount of requests you send.

In this post, we're going to set up a really basic form, then add a Vue watcher to detect when any part of a form updates. This is where we'd usually send an API request to persist changes. We'll then take a look at how to debounce this input to avoid too many network requests.

If you prefer to watch, there's a free screencast covering debouncing in Vue 3 over on Codecourse.

The problem

Ok, let's start with a really simple component with a form.

<template>
    <div>
        <label for="first_name">First name</label>
        <input type="text" id="first_name" v-model="form.first_name">
    </div>

    <div>
        <label for="last_name">Last name</label>
        <input type="text" id="last_name" v-model="form.last_name">
    </div>
</template>

<script setup>
	import { reactive } from 'vue'
  
    const form = reactive({
    	first_name: null,
    	last_name: null
    })
</script>

The form, represented by a Vue reactive will hold the first and last name.

Now let's watch for changes.

import { reactive, watch } from 'vue'

const form = reactive({
    first_name: null,
    last_name: null
})

watch(form, () => {
	console.log('Send API request')
})

By adding the watcher, any time first_name or last_name changes, the callback will be triggered and send an API request (or in this case, log to the console).

The problem here? Every single tiny update to the form will trigger an API request. If the user enters Alex Garrett-Smith as their name, that's 18 network requests just to update a first and last name!

Let's debounce!

First, install the lodash debounce package.

npm i lodash.debounce

Now we're going to import it, and wrap our watcher callback with it, specifying a millisecond wait time.

import { reactive, watch } from 'vue'
import debounce from 'lodash.debounce'

// ...

watch(form, debounce(() => {
	console.log('Send API request')
}, 500))

With this simple addition, the watch callback will wait to be executed until 500ms have elapsed since the last time it was invoked (or 500ms since it has not be invoked). Simply put, the user has 500ms in-between characters they type. You can increase or decrease this value as you need – experiment with it.

Improving it

The issue we have is that the entire watch callback is debounced. What if we need to add more code to the callback that we don't want debounced?

It's best to split this up so we only debounce the actual API call – so let's move this to a function.

// ...

const update = debounce(() => {
	console.log('Send API request')
}, 500)

watch(form, () => {
	update()
})

Now we can potentially add more to our watch callback to be immediately called, and still have the update function debounce.

So, that's debouncing input in Vue 3. A really simple way to avoid too many network requests (or other calls) when we're watching anything for changes.

Author
Alex Garrett-Smith