typescript 49 lines · 7 steps

A debounced search hook in React

A custom hook that debounces typing and cancels stale requests so search results stay fresh and ordered.

Explained by highlit
1import { useState, useEffect, useRef, useCallback } from "react";
2 
3interface SearchResult {
4 id: string;
5 title: string;
6}
7 
8export function useSearch(delay = 300) {
9 const [query, setQuery] = useState("");
10 const [results, setResults] = useState<SearchResult[]>([]);
11 const [loading, setLoading] = useState(false);
12 const abortRef = useRef<AbortController | null>(null);
13 
14 useEffect(() => {
15 const trimmed = query.trim();
16 if (!trimmed) {
17 setResults([]);
18 setLoading(false);
19 return;
20 }
21 
22 setLoading(true);
23 const handle = setTimeout(async () => {
24 abortRef.current?.abort();
25 const controller = new AbortController();
26 abortRef.current = controller;
27 
28 try {
29 const res = await fetch(`/api/search?q=${encodeURIComponent(trimmed)}`, {
30 signal: controller.signal,
31 });
32 const data: SearchResult[] = await res.json();
33 setResults(data);
34 } catch (err) {
35 if ((err as Error).name !== "AbortError") setResults([]);
36 } finally {
37 if (!controller.signal.aborted) setLoading(false);
38 }
39 }, delay);
40 
41 return () => clearTimeout(handle);
42 }, [query, delay]);
43 
44 const onChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
45 setQuery(e.target.value);
46 }, []);
47 
48 return { query, results, loading, onChange };
49}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Debouncing with setTimeout plus an effect cleanup prevents a request on every keystroke.
  2. 2An AbortController ref lets a new search cancel the previous in-flight one, avoiding out-of-order results.
  3. 3Guarding state updates against aborted requests keeps loading flags and results consistent.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A debounced search hook in React — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code