Debounced search prevents excessive API calls while typing.
Basic debounce hook:
1import { useState, useEffect } from "react";23function useDebounce<T>(value: T, delay: number): T {4 const [debouncedValue, setDebouncedValue] = useState(value);56 useEffect(() => {7 const timer = setTimeout(() => setDebouncedValue(value), delay);8 return () => clearTimeout(timer);9 }, [value, delay]);1011 return debouncedValue;12}
Search component:
1import { useState, useEffect } from "react";23function SearchPage() {4 const [query, setQuery] = useState("");5 const [results, setResults] = useState([]);6 const [loading, setLoading] = useState(false);7 const debouncedQuery = useDebounce(query, 500);89 useEffect(() => {10 if (!debouncedQuery) {11 setResults([]);12 return;13 }1415 setLoading(true);16 search(debouncedQuery)17 .then(setResults)18 .finally(() => setLoading(false));19 }, [debouncedQuery]);2021 return (22 <div>23 <input24 value={query}25 onChange={e => setQuery(e.target.value)}26 placeholder="Search..."27 />28 {loading && <Spinner />}29 <ul>30 {results.map(item => (31 <li key={item.id}>{item.title}</li>32 ))}33 </ul>34 </div>35 );36}
With useTransition:
1function SearchPage() {2 const [query, setQuery] = useState("");3 const [results, setResults] = useState([]);4 const [isPending, startTransition] = useTransition();56 const handleChange = (e) => {7 const value = e.target.value;8 setQuery(value);910 startTransition(() => {11 setResults(filterItems(value));12 });13 };1415 return (16 <div>17 <input value={query} onChange={handleChange} />18 <div style={{ opacity: isPending ? 0.6 : 1 }}>19 {results.map(item => <div key={item.id}>{item.title}</div>)}20 </div>21 </div>22 );23}
Benefits: