Exploration · Acme web client
Three ways to implement debounced search
Prompt
Show me three different ways to implement debounced search for the task
filter input in our React codebase, with tradeoffs for each.
export function TaskSearch() {
const [draft, setDraft] = useState('');
const [query, setQuery] = useState('');
useEffect(() => {
const id = setTimeout(() => setQuery(draft), 300);
return () => clearTimeout(id);
}, [draft]);
const { data } = useTasks({ search: query });
return (
<input
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="Filter tasks…"
/>
);
}
Zero new abstractions to learn
Logic duplicated everywhere search exists
Easy to step through in devtools
Two pieces of state for one conceptual value
No dependency or bundle change
Delay constant is buried in component body
Bundle impact: +0 kb
Testability: medium
Reuse: low
SSR safe: yes
// src/hooks/useDebounce.ts
export function useDebounce<T>(value: T, ms = 300): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), ms);
return () => clearTimeout(id);
}, [value, ms]);
return debounced;
}
// TaskSearch.tsx
const [draft, setDraft] = useState('');
const query = useDebounce(draft, 300);
const { data } = useTasks({ search: query });
Single import reused across filter, command bar, board search
One more file to maintain and document
Trivial to unit test with fake timers
Generic T hides intent slightly
Delay is a visible, tunable argument
Still re-renders on every keystroke
Bundle impact: +0.2 kb
Testability: high
Reuse: high
SSR safe: yes
import { useDebouncedCallback }
from 'use-debounce';
export function TaskSearch() {
const [query, setQuery] = useState('');
const onChange = useDebouncedCallback(
(next: string) => setQuery(next),
300,
{ leading: false, maxWait: 1000 },
);
const { data } = useTasks({ search: query });
return (
<input
defaultValue=""
onChange={(e) => onChange(e.target.value)}
/>
);
}
leading / trailing / maxWait handled for us
New runtime dependency to audit and update
Callback form skips intermediate re-renders
Uncontrolled input diverges from Acme form patterns
Well-tested edge cases (unmount, flush, cancel)
~1.4 kb gzipped for something we could own
Bundle impact: +1.4 kb
Testability: high
Reuse: high
SSR safe: yes