Swil
Frontend
InternalsJUL 09, 2026

Writing useDebouncedValue

A hand-written debounced-value hook, read line by line — the source you see is the source that runs.

The most common debounce in React isn’t a function you call — it’s a value you derive. useDebouncedValue gives you a copy of a fast-changing value that only settles once the changes stop, so everything downstream can just depend on the settled version.

Livewatch requests vs keystrokes
You typed
Request forsettled
keys 0requests 0

Every keystroke updates query instantly (the input stays responsive), but debouncedQuery only catches up after you pause. Bind the expensive work — a fetch, a filter, a router push — to the debounced value and the request count stays a fraction of the keystroke count.

The hook, line by line

What you see below is the exact source this page imports and runs.

useDebouncedValue.ts
import { useEffect, useState } from "react";

/**
 * Returns a copy of `value` that only updates after it has stopped changing for
 * `delay` ms. Each change schedules an update and cancels the previous one via
 * the effect cleanup — so rapid changes collapse into a single, settled value.
 *
 * Typical use: derive a debounced search term from a fast-changing input, then
 * depend on THAT for the expensive work (a fetch, a filter, a route push).
 */
export function useDebouncedValue<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    // Cleanup runs before the next effect (value/delay changed) and on unmount,
    // cancelling the pending update — this is what makes the debounce work.
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

Three moving parts. useState(value) seeds the debounced copy so the first render already has a value. The effect schedules setDebounced(value) after delay ms. The cleanup — clearTimeout — is the whole trick: because it runs before the next effect whenever value or delay changes, a new keystroke cancels the previous pending update, so only the last one in a burst survives.