Skip to main content

Timing

Debounce, throttle, animation-frame, and interval/timeout scheduling, with proper cleanup on unmount.

useDebouncedCallback​

Fires a callback only after the value stops changing for delay(ms). Useful for search inputs, autosave, etc. Leaving the field applies a waiting value at once (flush); Escape drops it (cancel).

Debounced value: (none)
Callback runs: 0
const [text, setText] = useState('');
const [debounced, setDebounced] = useState('');

useDebouncedCallback(() => setDebounced(text), { delay: 400 }, [text]);

The returned function also acts on a call that is still waiting: flush() runs it now and cancel() drops it. Both do nothing when no call is waiting. By default a waiting call is dropped when the component unmounts; pass flushOnUnmount: true to run it instead, for work that must not be lost when its UI goes away.

const save = useDebouncedCallback(
() => persist(draft),
{ delay: 1000, flushOnUnmount: true },
[draft],
);

<textarea
value={draft}
onChange={e => setDraft(e.target.value)}
onBlur={save.flush}
/>;

useDebouncedValue​

The value-shaped counterpart to useDebouncedCallback — symmetric with useThrottledValue's (value, delay) => value signature, for when all you need is the debounced value itself.

Live value: (none)
Debounced value: (none)
const [text, setText] = useState('');
const debounced = useDebouncedValue(text, 400);

useThrottledValue​

Applies the latest value only at delay(ms) intervals, even if it changes rapidly. Useful for scroll/resize handling.

x
Live value: 0
Throttled value (500ms): 0
const [value, setValue] = useState(0);
const throttled = useThrottledValue(value, 500);

useThrottledCallback​

Throttles a callback directly (unlike useThrottledValue, which throttles a value) — the natural fit for scroll/mousemove/resize handlers. Supports leading/trailing options.

Move your mouse over this box
Position: 0, 0
Handler calls (throttled to 200ms): 0
const onMouseMove = useThrottledCallback(
(x: number, y: number) => setPosition({ x, y }),
200,
);

<div onMouseMove={e => onMouseMove(e.clientX, e.clientY)} />;

useTimeout​

A setTimeout that doesn't go stale — the callback is read from a ref, delay === null pauses it (0 is a valid delay), and reset/clear let you restart or cancel imperatively.

const [open, setOpen] = useState(false);
const { reset, clear } = useTimeout(() => setOpen(false), open ? 2000 : null);

// Pause the auto-dismiss while hovered, restart it on mouse leave
<div onMouseEnter={clear} onMouseLeave={reset}>
Toast
</div>;

useInterval​

Dan Abramov's useInterval pattern — the callback is read from a ref so a fresh function every render doesn't reset the interval, only delay === null (pause) vs a number (running) does.

Count: 0
const [running, setRunning] = useState(false);
const [count, setCount] = useState(0);

useInterval(() => setCount(c => c + 1), running ? 1000 : null);

useRecursiveTimeout​

Repeats a callback using recursive setTimeout instead of setInterval. Pass null as delay to stop.

Elapsed ticks: 0s
RunningPaused
const [tick, setTick] = useState(0);

useRecursiveTimeout(
() => {
setTick(t => t + 1);
},
running ? 1000 : null,
);

useAnimationFrameCallback​

Coalesces repeated calls into one run per animation frame, with the arguments of the last call. Move the pointer over the box: events arrive faster than frames.

Mouse move events: 0
Callback runs (one per frame at most): 0
Last position: 0, 0
const [schedule, cancel] = useAnimationFrameCallback(
(x: number, y: number) => setPosition({ x, y }),
{ window: targetWindow }, // optional, defaults to the global window
);

window.addEventListener('scroll', () =>
schedule(window.scrollX, window.scrollY),
);

schedule(...args) requests a frame if none is pending. More calls before it fires replace the arguments, so the callback runs once per frame with the latest ones. cancel() drops the pending frame, and unmounting cancels it too. schedule and cancel keep their identity across renders, and the callback may call schedule again to repeat on every frame. The window option uses another window's requestAnimationFrame, for elements that live in an iframe.