Ever built a search input that triggers an API call on every single keystroke, only to watch your network tab explode? You need a debounce technique to pause API requests until the user finishes typing.
- Set up a fast development environment using Vite and React
- Fetch remote data from a public REST API
- Implement a debounce timer to limit excessive network requests
- Build a clean keyboard-friendly dropdown UI
Setting Up the Project and API Call
First, we need a basic React component that holds our search input value and our list of fetched results. We will use JSONPlaceholder as our mock REST API to fetch user data.
import { useState, useEffect } from 'react';
export default function SearchDropdown() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (query.trim() === '') {
setResults([]);
return;
}
fetch(`https://jsonplaceholder.typicode.com/users?name_like=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]);
return (
setQuery(e.target.value)}
placeholder="Search users..."
/>
{results.length > 0 && (
{results.map(user => (
- {user.name}
))}
)}
);
}This code successfully fetches data, but it fires a new request every single time a letter is typed.
Adding Debounce to Save API Calls
Debouncing means waiting for a brief pause in typing—like 300 milliseconds—before we actually run our search logic. We can achieve this easily inside a useEffect hook using standard JavaScript timers.
import { useState, useEffect } from 'react';
export default function SearchDropdown() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
const timer = setTimeout(() => {
if (query.trim() === '') {
setResults([]);
return;
}
fetch(`https://jsonplaceholder.typicode.com/users?name_like=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, 300);
return () => clearTimeout(timer);
}, [query]);
return (
setQuery(e.target.value)}
placeholder="Search users..."
/>
{results.length > 0 && (
{results.map(user => (
- {user.name}
))}
)}
);
}By clearing the timeout on every new keystroke, we ensure the fetch function only runs after the user pauses typing for 300 milliseconds.
Common mistakes
A frequent mistake is forgetting to clear the timeout cleanup function, which can lead to memory leaks or outdated state updates. Another issue is failing to handle loading states, leaving users wondering if their search is actually working.
Clone this repository to your local machine and try adding a loading spinner that appears while the API request is in flight.