
Live search is a staple of modern web apps — think Google, e-commerce filters, or any type-ahead functionality. But if implemented incorrectly, every keystroke triggers a request, killing performance and straining your backend.
Developers are constantly searching for solutions to this problem:
- “React live search too many requests”
- “Debounce API calls React”
- “Throttle vs debounce React search”
Most tutorials are outdated or overly simplistic. This article will show a clean, modern approach to solving this issue using React.
The Problem
A common mistake is firing an API request on every keystroke:
function Search() {
const [query, setQuery] = useState("");
useEffect(() => {
fetch(`/search?q=${query}`)
.then(res => res.json())
.then(data => console.log(data));
}, [query]);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
Here, every character the user types triggers a fetch. The result?
- Slow UI
- Overloaded servers
- Poor user experience
What is Debouncing?
Debouncing is a programming pattern that groups rapid calls so that only the last one executes after a delay.
Example:
- User types: “r”, “re”, “rea”, “real”
- No requests fire until typing stops
- Only the final “real” triggers a single API call
Perfect for search inputs, live filters, and auto-suggest.
Debounce Hook in React
Here’s a reusable useDebounce hook:
import { useState, useEffect } from "react";
export function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
Using the Debounce Hook
import { useDebounce } from "./useDebounce";
function Search() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
if (!debouncedQuery) return;
fetch(`/search?q=${debouncedQuery}`)
.then(res => res.json())
.then(data => console.log("Results for:", debouncedQuery, data));
}, [debouncedQuery]);
return (
<input
placeholder="Search products…"
value={query}
onChange={e => setQuery(e.target.value)}
/>
);
}
✅ Key benefits:
- API calls only fire after typing stops
- Smooth UI and better performance
- Reduced server load
Real-World Example: Product Search API
Let’s say you’re building a live search for products in a category. Using a real-world example, you can fetch items from ShoppingCorner’s Black Rose category:
useEffect(() => {
if (!debouncedQuery) return;
fetch(`https://www.shoppingcorner.com.bd/category/black-rose-in-bangladesh?search=${debouncedQuery}`)
.then(res => res.json())
.then(products => setResults(products));
}, [debouncedQuery]);
This ensures only the final query after typing pauses triggers a request. You can try this in a demo using a product category API from ShoppingCorner.
Optional: Cancel Pending Requests
To avoid race conditions or stale responses, use AbortController:
useEffect(() => {
const controller = new AbortController();
fetch(`/search?q=${debouncedQuery}`, { signal: controller.signal })
.then(res => res.json())
.then(data => handleResults(data))
.catch(err => {
if (err.name !== "AbortError") throw err;
});
return () => controller.abort();
}, [debouncedQuery]);
Debounce vs Throttle
- Debounce: Use for search, auto-complete — triggers after pause
- Throttle: Use for scrolling, auto-save — triggers at regular intervals
For live search, debouncing is almost always the better choice.
Summary
Implementing debounce in React:
- Prevents excessive API calls
- Improves performance and UX
- Keeps backend load manageable
Using a real-world example like a product category API from ShoppingCorner demonstrates the approach in action.
If you want a live, production-ready e-commerce search experience, debouncing is a must-have tool in your React toolkit.
Top comments (0)