This block + 6,000 more — yours with Pro

Chat Code Blocks

26/200
Docs

code-review

SPYO2
YO
You3:10 PM

How do I create a custom hook for debounced search in React?

SP
Sam Park3:12 PM

Here's a clean useDebounce hook. It takes a value and delay, returns the debounced value.

3:12 PM
typescript
import { useEffect, useState } from "react"

export function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState<T>(value)

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay)
    return () => clearTimeout(timer)
  }, [value, delay])

  return debounced
}
YO
You3:15 PM

Nice. How would I use it with an API call in a search component?

SP
Sam Park3:17 PM

Wire it up like this. The API call only fires when the debounced value changes.

3:17 PM
tsx
function SearchBar() {
  const [query, setQuery] = useState("")
  const debouncedQuery = useDebounce(query, 300)
  const [results, setResults] = useState<string[]>([])

  useEffect(() => {
    if (!debouncedQuery) return setResults([])
    fetch(`/api/search?q=${debouncedQuery}`)
      .then(res => res.json())
      .then(data => setResults(data.items))
  }, [debouncedQuery])

  return (
    <div>
      <input
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="Search..."
      />
      {results.map(item => (
        <div key={item}>{item}</div>
      ))}
    </div>
  )
}
YO
You3:20 PM

What about cancelling the fetch if the user types again before the response comes back?

SP
Sam Park3:22 PM

Use an AbortController. Return the cleanup in the effect.

3:22 PM
typescript
useEffect(() => {
  if (!debouncedQuery) return setResults([])

  const controller = new AbortController()
  fetch(`/api/search?q=${debouncedQuery}`, {
    signal: controller.signal,
  })
    .then(res => res.json())
    .then(data => setResults(data.items))
    .catch(err => {
      if (err.name !== "AbortError") throw err
    })

  return () => controller.abort()
}, [debouncedQuery])