A Guide to Debouncing in JavaScript and React | Create a Custom Hook
Introduction
In our daily surfing of the web, we’ve encountered dynamic web features like auto-complete search boxes (as in Google), drag-and-drop functionality, and smooth scrolling effects. These seamless interactions often rely on techniques like debouncing
and throttling
to ensure a smooth user experience. These techniques are crucial for handling tasks like API calls, scrolling effects, etc., In this article, we will look into implementing the debounce functionality in JavaScript and later convert it into a reusable hook in React.
What is Debouncing?
Debouncing is a technique used in web development to control the rate at which a particular function or action is triggered in response to an event, typically user input or some form of interaction. It ensures that the function is only executed once after a specified period of inactivity following the event, even if the event itself occurs multiple times in quick succession.
Why Debouncing?
Let’s consider a scenario of a social media application where you need to find your friends’ named John
and you need to see the results in real time without pressing any button. So whenever you enter a character in the search bar, an API call will be fired to fetch the friend list. But we don’t want that to happen as the API calls to the server is costly and we need to optimize it for efficient usage of the backend resources. To solve this, debounce
comes in handy. We will implement the debounce functionality in such a way that the API call won’t be fired until a certain time has elapsed since the last input. Debouncing can also be used in tasks like handling scroll events or preventing excessive form submissions.
Debouncing in JavaScript
In this section, we will see how to implement the debouncing functionality in JavaScript.
We know the setTimeout
function in JavaScript will execute a callback after a certain delay. For Debouncing, we will make use of the setTimeout
as it would be a good fit. As we know, setTimeout
takes in 2 parameters, the callback function and the delay. It returns a value which is the id
of the corresponding setTimeout
. As and when the user is typing, we will make sure to clear the timeout function so the callback function won’t be fired until there is no input from the user for the duration of the delay.
const debounce = (cbFn, delay = 500) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
cbFn(...args);
}, delay);
};
};
In the above, the debounce
function takes in the cbFn
as the first parameter and the delay as the second parameter with a default value of 500ms. It returns a function that can be called further in our application. When called, the function will clear any previously set timeout and create a brand-new setTimeout
function with the desired delay. When there is no input from the user for the desired delay, the callback function will be executed.
To see the debouncing functionality in action, let’s create a simple input and add the debouncing functionality to it.
Create index.html
and add the following content.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Debounce</title>
</head>
<body>
<input type="text">
<p>The entered value will appear below when there is no input for 1 second</p>
<div>Entered Value:</div>
<script src="script.js"></script>
</body>
</html>
Now add the JavaScript in the script.js
file.
const input = document.querySelector("input");
const div = document.querySelector("div");
const debounce = (cbFn, delay = 500) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
cbFn(...args);
}, delay);
};
};
const callDebouncedFunction = debounce((args) => {
div.innerText = `Entered Value: ${args}`;
}, 1000);
input.addEventListener("input", (e) => {
callDebouncedFunction(e.target.value);
});
Here, we have created a simple input field and a <p>
element and a <div>
element which shows the entered input value. In the script.js
file, we have added an event listener to the input
field. We have created a callDebouncedFunction
by invoking the debounce
function and passing in a callback function that updates the div
element's text content with the entered value. The debounce delay is set to 1000 milliseconds (1 second) in this case. So the div
will be updated only when there is no input from the user for 1 second.
Check the demo for the above in Codepen.
Implementing Debouncing in React
Now, let’s move on to implementing the debouncing functionality in React. Bootstrap a basic react app using Vite using the below command.
# npm
npm create vite@latest react-debounce -- --template react
# Yarn
yarn create vite react-debounce -- --template react
Once the above step is done, Run the below commands to start the local dev server.
# npm
npm install
npm run dev
# yarn
yarn
yarn dev
Let’s implement the debouncing functionality in react. In theApp.jsx
file add the following content.
import { useState } from "react";
export default function App() {
const [debouncedValue, setDebouncedValue] = useState("");
const debounce = (cbFn, delay = 250) => {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
cbFn(...args);
}, delay);
};
};
const handleChange = debounce((v) => {
setDebouncedValue(v);
}, 1000);
return (
<div style={{ display: "flex", gap: "5px", flexDirection: "column" }}>
<input type="text" onChange={(e) => handleChange(e.target.value)} />
<p style={{ fontSize: "24px" }}>
The entered value will appear below when there is no input for 1 second
</p>
<p style={{ fontSize: "24px" }}>
Debounced Value:{" "}
<span style={{ fontWeight: "bold" }}>{debouncedValue}</span>
</p>
</div>
);
}
Similar to the vanilla implementation, we have created an input
field.
We use the useState
hook to manage the debouncedValue
. This state is displayed as the result after debouncing.
We have defined the debounce
function and hanldeChange
which calls the debounce
function. The handleChange
method is attached to the onChange
of the input
field. The debounce
delay is set to 1000ms.
This ensures that the state updates only when you've paused for a second, avoiding constant updates.
The Custom Debounce Hook
In React, custom hooks are your superpower for encapsulating and reusing functionality across components. In this section, we will implement the custom useDebounce
hook.
Create the useDebounce.jsx
file and add the below content.
import { useEffect, useState } from "react";
export default function useDebounce(value, delay = 250) {
const [debouncedValue, setDebouncedValue] = useState("");
useEffect(() => {
const timeoutId = setTimeout(() => setDebouncedValue(value), delay);
return () => {
clearTimeout(timeoutId);
};
}, [value, delay]);
return debouncedValue;
}
The custom useDebounce
hook takes two parameters: the value we want to debounce (value
) and an optional delay time (delay
) in milliseconds with a default value of 250ms
. Inside the hook, we are making use of useState
to manage the state of the debounced value. In the useEffect
's body, we set the timer using setTimeout
to update the debounced value after the delay. This will run whenever one of the values of the dependency array (value
and delay
) changes. In the useEffect
's cleanup function, we clear the previous timers if the value
or delay
changes before the timer completes, preventing multiple quick updates. This ensures that the value is only updated once the user has paused their input for the desired delay.
Let’s integrate this hook into the App
component.
import { useState } from "react";
import useDebounce from "./useDebounce";
export default function App() {
const [value, setValue] = useState("");
const debouncedValue = useDebounce(value, 1000);
return (
<div style={{ display: "flex", gap: "5px", flexDirection: "column" }}>
<input
type="text"
value={value}
onChange={(e) => {
setValue(e.target.value);
}}
/>
<p style={{ fontSize: "24px" }}>
The entered value will appear below when there is no input for 1 second
</p>
<p style={{ fontSize: "24px" }}>
Debounced Value:{" "}
<span style={{ fontWeight: "bold" }}>{debouncedValue}</span>
</p>
</div>
);
}
We have defined a state variable called value
to manage the input
element. We are passing this state to the useDebounce
hook and also passing the delay of one second. That’s it, we have implemented the debouncing functionality. To see this in action, check the codesandbox here.
Conclusion
In this article, we have seen the basic definition of debouncing and its implementation in vanilla JS. Also, we have converted that into a reusable react hook which can be reused across the components. This functionality can be applied in many use cases like Real-Time searching, Input validation, Infinite scrolling and any use case where you need to delay an action for a desired time. You can take this approach and integrate it into your applications. Thanks for Reading.
Subscribe to my newsletter
Read articles from Nirmal Kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Nirmal Kumar
Nirmal Kumar
Full stack software Engineer https://linktr.ee/NirmalKumar