useDebouncedValue hook
| 1 min read
You have a search input. The user is typing fast. You don’t want to fire off an API call on every keystroke. You debounce.
import { useEffect, useState } from "react";
export function useDebouncedValue<T>(value: T, delay = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
Usage:
function Search() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebouncedValue(query, 400);
useEffect(() => {
if (!debouncedQuery) return;
fetch(`/api/search?q=${debouncedQuery}`).then(/* ... */);
}, [debouncedQuery]);
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
The user keeps typing → query updates on every keystroke → debouncedQuery only updates 400ms after they stop. The fetch fires once, not 12 times.
The cleanup function in useEffect is doing the heavy lifting — every new keystroke cancels the pending timeout before it fires.