patterntypescriptModerate
Real-Time Search Requires Debouncing Queries and Cancelling Stale Requests
Viewed 0 times
real-time searchdebounceAbortControllerrace conditionstale resultssearch as you typefetch cancellation
Problem
A search-as-you-type feature fires an API request on every keystroke. Responses arrive out of order (a later query resolves before an earlier one) and the UI displays stale results for the current input.
Solution
Debounce the search trigger and cancel in-flight requests using AbortController when a new search supersedes them.
import { useEffect, useRef, useState } from 'react';
function useRealtimeSearch(query: string, delay = 300) {
const [results, setResults] = useState<SearchResult[]>([]);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
if (!query.trim()) { setResults([]); return; }
const timer = setTimeout(async () => {
// Cancel previous in-flight request
abortRef.current?.abort();
abortRef.current = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: abortRef.current.signal
});
const data = await res.json();
setResults(data.results);
} catch (err) {
if ((err as Error).name !== 'AbortError') throw err;
}
}, delay);
return () => clearTimeout(timer);
}, [query, delay]);
return results;
}Why
Network requests complete in non-deterministic order. Without cancellation, the response to 'ca' may arrive after the response to 'cat', overwriting the correct results. AbortController signals the fetch infrastructure to ignore the earlier response.
Gotchas
- AbortError is not a real error — always check for it before re-throwing.
- Debounce delay of 200–400ms balances responsiveness against server load for typing patterns.
- For WebSocket-based search, send a cancel message to the server to stop expensive streaming queries.
- Cache results by query string to avoid re-fetching the same query (use a Map with LRU eviction).
Revisions (0)
No revisions yet.