Search as you type
Build a search box that waits until typing pauses (a custom useDebounce hook), loads results from a pretend API in an effect, ignores stale responses, and shows loading and empty states.
Debouncing with an effect
Search-as-you-type looks simple from the outside but hides several classic problems: too many requests, out-of-order responses, and missing empty or error states. Solving them here prepares you for any feature that talks to a server while the user is still interacting.
useDebounce(value, ms) returns a copy of value that only updates after the value has stopped changing for ms. Inside, an effect starts a timer whenever the value changes; the cleanup cancels the previous timer. Typing fast therefore keeps resetting the timer, and only the final pause lets it through.
function useDebounce(value, ms) {
const [settled, setSettled] = useState(value);
useEffect(() => {
const t = setTimeout(() => setSettled(value), ms);
return () => clearTimeout(t);
}, [value, ms]);
return settled;
}
const query = useDebounce(text, 300); // changes 300 ms after typing stopsThe search itself is a second effect that depends on the debounced query. Keeping them separate means the input stays instant (it uses text), while the expensive work waits.