Build a useDebounce Hook in React

Build a useDebounce Hook that returns a value only after rapid updates stop, and use it to cut down search requests in React.

6 min read

A useDebounce Hook returns a value that updates only after the source value stops changing for a set delay. It is the standard way to prevent a search box from firing a request on every keystroke. You keep the immediate value for the input and the delayed value for the expensive work.

Delay with a timer

Debouncing waits for quiet. When the value changes, start a timeout.

If the value changes again before the timeout finishes, cancel the old timer and start a new one. The debounced value updates only after the delay passes without another change.

Build the Hook

App.jsxApp.jsx
import { useEffect, useState } from "react";
 
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  return debouncedValue;
}

The Effect runs each time value or delay changes. Its cleanup clears the pending timer, so only the most recent value survives to update the state.

Skipping the cleanup would let an older timer still fire and overwrite the newer value. useState initializes the debounced value to the first source value, so there is no empty window on the initial render.

Use it for a search input

App.jsxApp.jsx
function SearchBox() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 400);
  return (
    <div>
      <input value={query} onChange={(event) => setQuery(event.target.value)} />
      <p>Searching for: {debouncedQuery}</p>
    </div>
  );
}

The input shows query instantly, but debouncedQuery catches up only after the user pauses for 400 milliseconds. In a real app, you would start the request from debouncedQuery instead of displaying it, so the network only fires once the user stops typing.

Debounce vs useDeferredValue

Debounce adds a real timer delay, which is what you want when the goal is fewer requests or fewer expensive actions. useDeferredValue defers a value to keep rendering responsive and has no fixed delay, but it does not prevent extra network requests by itself.

ConcernuseDebounce timeruseDeferredValue
What it controlsWhen a value updatesWhen rendering happens
DelayFixed, chosen by youNone, adapts to device
Reduces network requestsYesNo

Use a timer when you want to wait for the user to stop typing. Use useDeferredValue when a slow UI part should lag behind without blocking input. The full comparison covers the decision.

When to reach for debounce

  • Search and autocomplete requests while the user types.
  • Saving a form after the user pauses.
  • Any expensive action that should not run on every change.

If the user pauses for the full delay, the debounced value updates once, and only then does the work run. The useDebounce Hook is not needed for values that only drive rendering. See how to create a custom Hook for the extraction steps.

Rune AI

Rune AI

Key Insights

  • useDebounce returns a value that updates after the source stops changing.
  • Use setTimeout inside an Effect and clearTimeout in cleanup.
  • Keep the immediate value for the input and the debounced value for the request.
  • Prefer useDeferredValue when deferring rendering, not requests.
  • Choose a delay around 300 to 500 milliseconds for search.
RunePowered by Rune AI

Frequently Asked Questions

Why does the useDebounce Hook need cleanup?

Cleanup clears the pending timer before the next change. Without it, every keystroke would schedule another timeout and the old value could still update later.

What is the difference between useDebounce and useDeferredValue?

useDebounce waits a fixed amount of time before updating a value. useDeferredValue defers rendering with no fixed delay and does not prevent extra network requests.

What delay should I choose for a search input?

Around 300 to 500 milliseconds is common. Longer feels laggy, and shorter defeats the point of waiting for the user to pause.

Conclusion

A useDebounce Hook keeps an immediate value and a delayed copy in sync through a timer. Use it to wait for the user to stop typing before firing a search or saving, and prefer useDeferredValue when the goal is rendering, not rate limiting.