code-review
How do I create a custom hook for debounced search in React?
Here's a clean useDebounce hook. It takes a value and delay, returns the debounced value.
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
}Nice. How would I use it with an API call in a search component?
Wire it up like this. The API call only fires when the debounced value changes.
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>
)
}What about cancelling the fetch if the user types again before the response comes back?
Use an AbortController. Return the cleanup in the effect.
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])