In software development, certain tasks can be resource-intensive. For example, an API that searches a list of users, and we can’t afford to call it too often. We want to ensure that the search is only initiated once the entire search query has been entered.
This is where debouncing comes in – it’s a technique used in software development to regulate the frequency at which heavy tasks like this API are executed.
Problem
Let’s check an example of code implemented without debouncing and how it is harmful to our web app.
import React, { useEffect, useState } from "react";
const App = () => {
const [searchQuery, setSearchQuery] = useState("");
const [suggestions, setSuggestions] = useState([]);
useEffect(() => {
getSuggestions(searchQuery);
}, [searchQuery]);
const getSuggestions = (value) => {
fetch(`https://demo.dataverse.org/api/search?q=${value}`)
.then((res) => res.json())
.then((json) => setSuggestions(json.data.items));
};
return (
<div style={{ textAlign: "center" }}>
<h2>Debouncing in React JS</h2>
<div style={{ width: "40%", margin: "auto" }}>
<input
type="text"
className="search"
placeholder="Search here...."
onChange={(e) => setSearchQuery(e.target.value)}
style={{ width: "100%", height: "25px", fontSize: "20px" }}
/>
{suggestions.length > 0 && (
<div className="autocomplete">
{suggestions.map((el, i) => (
<div key={i} style={{ textAlign: "left" }}>
<span>{el.name}</span>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default App;
We used the onChange event with a text box to get the current value of the text box, and we stored this value in the state searchQuery
.
We passed the state searchQuery
as a dependency to useEffect hooks. So, when our state searchQuery
changes, the useEffect runs. And our useEffect hook contains the getSuggestions()
function where we call an API.
In short, each character type in the search box will trigger an API call.
If we check the network tab in the browser, we can see for each letter we typed in the search box, an API call was made.

This is harmful for the performance of our web app beacuse if the user types 50 or 1000 characters, it will make 50 or 100 API calls. This amount of API requests is not good for the server as well.
Let’s fix it using debouncing.
How to Implement Debouncing in React?
We will implement debouncing on an input box with an onChange
event. So when we type in the search box for each keypress
, an API call will be made.
We can implement debouncing in React JS in multiple ways.
- Using cleanup function
- Using lodash
1. Using cleanup function
In this method, we will use the useEffect
cleanup function.
What is cleanup function?
In useEffect Hook, we can return a function, which returns a function where cleanup happens. The cleanup function removes unnecessary and unwanted behaviors and prevents memory leaks.
The cleanup function runs when the component unmounts and before executing the next scheduled effect.
Let’s perform debouncing on the previous example.
import React, { useEffect, useState } from "react";
const App = () => {
const [searchQuery, setSearchQuery] = useState("");
const [suggestions, setSuggestions] = useState([]);
useEffect(() => {
let timeoutId = setTimeout(() => {
if (searchQuery) {
getSuggestions(searchQuery);
} else {
setSuggestions([]);
}
}, 500);
return () => {
clearTimeout(timeoutId);
};
}, [searchQuery]);
const getSuggestions = (value) => {
fetch(`https://demo.dataverse.org/api/search?q=${value}`)
.then((res) => res.json())
.then((json) => setSuggestions(json.data.items));
};
return (
<div style={{ textAlign: "center" }}>
<h2>Debouncing in React JS</h2>
<div style={{ width: "40%", margin: "auto" }}>
<input
type="text"
className="search"
placeholder="Search here...."
onChange={(e) => setSearchQuery(e.target.value)}
style={{ width: "100%", height: "25px", fontSize: "20px" }}
/>
{suggestions.length > 0 && (
<div className="autocomplete">
{suggestions.map((el, i) => (
<div key={i} style={{ textAlign: "left" }}>
<span>{el.name}</span>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default App;
In the above example, we used the cleanup function to perform debouncing.
Let’s understand it step by step.
1. Create timeout function
We created a timeout function in the useEffect hook; the timeout function has an interval of 500 milliseconds. That means it will trigger after 500 milliseconds.
The setTimeout()
returns ID as a positive integer value that identifies the timer created by the call to setTimeout()
. This value can be passed to clearTimeout()
to cancel the timeout.
But we have an issue because we are using the state searchQuery
as a dependency in our useEffect hook. So what it does is it will create a timeout function for each character we typed in, and each will be executed after 500 ms from when its created.
Somehow we need to remove these unwanted timeout events. Fortunately, JavaScript has the built-in method clearTimeout
function to cancel timeouts. Another issue is when we need to remove the timeout call. For that, we can use the cleanup function, which helps us to remove the previous timeout on a new timeout call.
So, let’s create a cleanup function that cancels previous timeouts.
2. Return cleanup function
We returned the cleanup function that contains code for cancelling the last timeout event.
Let’s check network tab.

As you can see, just one API call is made after using debouncing.
Alternatively, you can use the loadash npm package to perform debouncing.
2. Using lodash
Lodash provides a debounce method to limit the execution rate of a particular code block. Using this method, we can limit our API calls.
Let’s see how can we do it:
import React, { useState } from "react";
import { debounce } from "lodash";
const App = () => {
const [suggestions, setSuggestions] = useState([]);
const getSuggestions = debounce((value) => {
fetch(`https://demo.dataverse.org/api/search?q=${value}`)
.then((res) => res.json())
.then((json) => setSuggestions(json.data.items));
}, 500);
return (
<div style={{ textAlign: "center" }}>
<h2>Debouncing in React JS</h2>
<div style={{ width: "40%", margin: "auto" }}>
<input
type="text"
className="search"
placeholder="Search here...."
onChange={(e) => getSuggestions(e.target.value)}
style={{ width: "100%", height: "25px", fontSize: "20px" }}
/>
{suggestions.length > 0 && (
<div className="autocomplete">
{suggestions.map((el, i) => (
<div key={i} style={{ textAlign: "left" }}>
<span>{el.name}</span>
</div>
))}
</div>
)}
</div>
</div>
);
};
export default App;
Here we called API in lodash’s debounce function and set an interval of 500 ms. And called this function on the onChange
event of the text box.
Conclusion
Now that you understand the concept and purpose of the debounce function, you can see how simple it is to use.
For example, when we enter a search query into an input field, the results will only be displayed 500 milliseconds after we’ve stopped typing – all thanks to debouncing.
Debouncing has many practical applications. It can be used to prevent excessive API calls and to ensure that form data is only submitted once.
Learn More:
- Check if an Element is Focused in React
- Open a Link in a New Tab in React
- Handle Double Click Events in React
- Pass a Component as Props in React
- Component Definition is Missing Display Name in React
- Detect the Browser in React
- Check if a Checkbox is Checked in React
- Format a Number with Commas in React
- Capitalize First Letter of a String in React
- The useState set Method Not Reflecting a Change Immediately