Jordan Smalls

Software Engineer

Debounce a search input

Delay firing a search callback until typing pauses, with a cancel on unmount.

The hook stores the latest callback in a ref so the debounce timer always calls the newest version without needing to reset itself. The delay defaults to 300ms.

typescript
import { useCallback, useEffect, useRef } from 'react'

export function useDebouncedCallback<Args extends unknown[]>(
  callback: (...args: Args) => void,
  delay = 300,
) {
  const callbackRef = useRef(callback)
  const timeoutRef = useRef<ReturnType<typeof setTimeout>>()

  useEffect(() => {
    callbackRef.current = callback
  }, [callback])

  useEffect(() => {
    return () => clearTimeout(timeoutRef.current)
  }, [])

  return useCallback(
    (...args: Args) => {
      clearTimeout(timeoutRef.current)
      timeoutRef.current = setTimeout(() => callbackRef.current(...args), delay)
    },
    [delay],
  )
}

In a search input, wire the debounced function to onChange. The input stays fully controlled and responsive; only the search request itself is delayed.

typescript
function SearchBox({ onSearch }: { onSearch: (query: string) => void }) {
  const [value, setValue] = useState('')
  const debouncedSearch = useDebouncedCallback(onSearch, 300)

  return (
    <input
      value={value}
      onChange={(e) => {
        setValue(e.target.value)
        debouncedSearch(e.target.value)
      }}
      placeholder="Search..."
    />
  )
}