React - Search (AJAX)

Overview

Estimated time: 25–35 minutes

Build a debounced search box that queries an API, cancels stale requests, and renders results as you type.

Try it: Debounced Search

View source
const { useEffect, useRef, useState } = React;
function Search(){
  const [q, setQ] = useState('');
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const tRef = useRef(null);
  const ctlRef = useRef(null);
  useEffect(() => {
    if (tRef.current) clearTimeout(tRef.current);
    if (!q) { setItems([]); setError(''); setLoading(false); return; }
    tRef.current = setTimeout(async () => {
      try {
        setLoading(true); setError('');
        if (ctlRef.current) ctlRef.current.abort();
        ctlRef.current = new AbortController();
        const res = await fetch(`https://dummyjson.com/products/search?q=${encodeURIComponent(q)}`, { signal: ctlRef.current.signal });
        if (!res.ok) throw new Error('Network error');
        const data = await res.json();
        setItems(data.products || []);
      } catch (e) {
        if (e.name !== 'AbortError') setError(e.message || 'Error');
      } finally {
        setLoading(false);
      }
    }, 400);
    return () => { if (tRef.current) clearTimeout(tRef.current); };
  }, [q]);
  return (
    <div>
      <input placeholder="Search products" value={q} onChange={e => setQ(e.target.value)} />
      {loading && <span style={{marginLeft:8}}>Loading...</span>}
      {error && <div style={{color:'salmon'}}>{error}</div>}
      <ul>{items.map(p => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('try-search')).render(<Search />);

Syntax primer

  • Debounce input by delaying the fetch until typing pauses.
  • Cancel previous fetch with AbortController to avoid race conditions.

Common pitfalls

  • Firing a request on every keystroke without debounce; wastes bandwidth and thrashes UI.

Exercises

  1. Show a result count and no-results message.
  2. Highlight query matches in results.