Debounce search input

Fetch on a debounced value, not on every keystroke.

From frontend-patterns, “Custom Hooks Patterns: Debounce Hook”.

Same typing, fewer requests

Type, or press the button to type at human pace.

Fetch per keystrokeBad

0 requests

No timer

No search yet

    useEffect(() => { performSearch(query) }, [query])
    useDebounce(q, 500)Good

    0 requests

    idle

    No search yet

      if (debouncedQuery) performSearch(debouncedQuery)
      export function useDebounce<T>(value: T, delay: number): T {
        const [debouncedValue, setDebouncedValue] = useState<T>(value)
      
        useEffect(() => {
          const handler = setTimeout(() => setDebouncedValue(value), delay)
          return () => clearTimeout(handler) // every new keystroke cancels the last timer
        }, [value, delay])
      
        return debouncedValue
      }
      
      const [searchQuery, setSearchQuery] = useState('')
      const debouncedQuery = useDebounce(searchQuery, 500)
      
      useEffect(() => {
        if (debouncedQuery) performSearch(debouncedQuery)
      }, [debouncedQuery])

      Pick the delay

      Set a delay, then replay. Each run is logged below.

      useDebounce(query, 500)Trade-off

      0 requests

      – results after last key

      idle
      Replay runs
      DelayRequestsResults after last key
      Replay to log a run.
      const debouncedQuery = useDebounce(searchQuery, 500)

      Late answers win

      Press Type a few times; stale answers can win.

      Fetch per keystrokeBad

      No requests yet

        No search yet

          useEffect(() => { performSearch(query) }, [query])
          useDebounce(q, 500)Good

          No requests yet

            No search yet

              useDebounce(query, 500)
              useEffect(() => {
                if (!debouncedQuery) return
                let ignore = false
                performSearch(debouncedQuery).then((r) => { if (!ignore) setResults(r) })
                return () => { ignore = true } // a pause longer than the delay can still race
              }, [debouncedQuery])

              Why it matters, pros and cons

              Why it matters

              Input changes faster than a server answers. Fetch only what holds still.

              Pros

              • One request per burst, not per key.
              • Fewer in-flight requests to race.
              • Works for any fast-changing value.

              Cons

              • Every result arrives at least delay late.
              • In-memory filtering needs no debounce at all.
              • No ordering guarantee; still ignore stale responses.