How to Build a Debounced Search Input in React.js
Debounced Search Input in React.js
Build a search bar that calls API only after user stops typing using
useEffect
and
setTimeout
or a custom hook.
What is Debouncing?
Debouncing is a performance optimization technique. When a user types quickly in a search input, making an API call on every key press is the wrong approach. By using debounce, we only call the API after the user has stopped typing.
"Do not call the API on every key press — call it only after the user stops typing."
Why Debounced Search is Needed?
Suppose a user types:
r
re
rea
reac
react
If we make an API call on every character, 5 API calls will be triggered. But by using debounce, only one API call is made on the final search keyword.
React Debounced Search Flow
1. User Types
The input state gets updated.
2. Timer Starts
A setTimeout delay begins.
3. Clear Old Timer
If the user types again, the old timer is cleared.
4. API Call
The API is called only after the user stops typing.
Example 1: Debounced Search Input
import React, { useEffect, useState } from "react";
export default function DebouncedSearch() {
const [searchTerm, setSearchTerm] = useState("");
const [debouncedValue, setDebouncedValue] = useState("");
const [results, setResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(searchTerm);
}, 500);
return () => {
clearTimeout(timer);
};
}, [searchTerm]);
useEffect(() => {
if (!debouncedValue.trim()) {
setResults([]);
return;
}
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(
`https://jsonplaceholder.typicode.com/users?name_like=${debouncedValue}`
);
const data = await response.json();
setResults(data);
} catch (error) {
console.error("Search API Error:", error);
} finally {
setLoading(false);
}
};
fetchData();
}, [debouncedValue]);
return (
<div className="mx-auto max-w-xl rounded-xl border border-gray-200 bg-white p-6 shadow-sm">
<h2 className="mb-4 text-2xl font-bold text-gray-900">
Debounced Search Input
</h2>
<input
type="text"
placeholder="Search users..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-gray-900 outline-none focus:border-blue-500"
/>
{loading && <p className="mt-4 text-sm text-gray-600">Searching...</p>}
<div className="mt-5 space-y-3">
{results.map((user) => (
<div key={user.id} className="rounded-lg border border-gray-200 p-4">
<h3 className="font-semibold text-gray-900">{user.name}</h3>
<p className="text-sm text-gray-600">{user.email}</p>
</div>
))}
</div>
</div>
);
}
Code Explanation
1. searchTerm State
This state stores the value typed by the user.
2. debouncedValue State
This is the final value that gets updated only after the delay has completed.
3. clearTimeout
If the user types again, the old timer gets cancelled.
Example 2: Custom useDebounce Hook
import React, { useEffect, useState } from "react";
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
export default function SearchWithCustomHook() {
const [query, setQuery] = useState("");
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const debouncedQuery = useDebounce(query, 600);
useEffect(() => {
if (!debouncedQuery.trim()) {
setUsers([]);
return;
}
async function searchUsers() {
try {
setLoading(true);
const res = await fetch(
`https://jsonplaceholder.typicode.com/users?name_like=${debouncedQuery}`
);
const data = await res.json();
setUsers(data);
} catch (error) {
console.log("Error:", error);
} finally {
setLoading(false);
}
}
searchUsers();
}, [debouncedQuery]);
return (
<div className="mx-auto max-w-xl p-6">
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Type to search..."
className="w-full rounded-lg border border-gray-300 px-4 py-3"
/>
{loading && <p className="mt-3">Loading...</p>}
{users.map((user) => (
<div key={user.id} className="mt-3 rounded-lg border p-3">
<strong>{user.name}</strong>
<p>{user.email}</p>
</div>
))}
</div>
);
}
Best Practices
- Keep the debounce delay between 300ms and 700ms.
- Avoid making an API call when the search query is empty.
- Always show a loading state.
- Always add proper error handling.
- In large applications, creating a custom hook is the better approach.
Interview Answer
In a Debounced Search Input, we optimize API calls by using setTimeout to delay the request. Instead of calling the API on every keystroke, we wait for the user to stop typing. If the user types again within the delay period, clearTimeout cancels the old timer and a new one starts. This reduces unnecessary API calls, improves performance, and reduces server load.
More In React js
Read more articles from the same category or open the full category archive directly.
React js
React 19 Features Explained with React 18 vs React 19 Comparison
React js
React Interview Questions and Answers 2026 | SSR, CSR, Hooks, Virtual DOM
Career
AI Courses for Software Developers in 2026: Best Course, Salary & Career Path
On This Page
Recent Posts
React 19 Features Explained with React 18 vs React 19 Comparison
7 May 2026
React Interview Questions and Answers 2026 | SSR, CSR, Hooks, Virtual DOM
7 May 2026
AI Courses for Software Developers in 2026: Best Course, Salary & Career Path
30 March 2026
Bharat Ratna Award Winners List 1954 to 2024: History, Facts and Full List
26 March 2026
Nobel Prize 2025 Winners, History, Categories, Indian Winners & Important Facts (Hindi + English)
26 March 2026
JavaScript ES6+ Interview Questions & Concepts (Destructuring, Spread, Rest)
21 March 2026
JavaScript Basics Explained: Variables, Data Types & Functions with Examples
21 March 2026
JavaScript Arrays Explained with Examples Part 2 (Complete Beginner Guide)
20 March 2026
JavaScript Arrays Explained with Examples (Complete Beginner Guide)
20 March 2026
Callback vs Promises in JavaScript
20 March 2026