mrkeyoor.com_
Sat 08 Aug 17:42 UTC
npmWeb Frontendupdated 08 Aug 2026

use-debounce

use-debounce is a small React hook package for delaying rapidly changing values or callbacks until activity settles. It exports useDebounce for values, useDebouncedCallback for functions, and useThrottledCallback for rate-limited work. Debounced handles expose cancel, flush, and isPending methods, with leading, trailing, maximum-wait, and equality options. It manages timer identity across renders but does not manage network cancellation or stale responses for you.

Verdict

A focused and well-sized choice for React teams that need more than a hand-written timeout. The timer API is the easy part; install it only if your code also handles object identity, teardown policy, and stale async results deliberately.

API stability4/5The three core hooks and the cancel, flush, isPending, leading, trailing, maxWait, and equality concepts have remained consistent across many releases. Version 10.1.1 exports both ESM and CommonJS entry points with TypeScript declarations. Major React-era releases have occurred, so upgrades should still run hook behavior and fake-timer tests.
Docs4/5The README covers value and callback debouncing, object equality, synthetic and native events, returned values, cancellation, flushing, pending state, maximum wait, leading and trailing calls, and throttling, with linked sandboxes. A few examples use older React rendering and testing styles, and async cancellation and race handling are not developed enough.
Maintenance5/5npm serves version 10.1.1, the repository was pushed in August 2026, is not archived, and has 14 open issues and pull requests. The package has no runtime dependencies, publishes modern and CommonJS outputs, and keeps a linked changelog, all of which reduce maintenance risk for a compact hook library.
Ecosystem5/5The package recorded 7,054,050 npm downloads in the fetched week and has 3,383 GitHub stars. It works with React as an intentionally broad peer, includes TypeScript declarations, and covers the common debounce and throttle variants. Its narrow focus means fewer integrations than a full hooks suite, but also less policy and code to absorb.

Use it if

  • You need to delay search, autosave, validation, analytics, resize, or scroll work in React components
  • You want a hook-safe callback with cancel, flush, isPending, leading, trailing, and maxWait behavior
  • You need either debouncing or throttling behind a consistent API without pulling all of Lodash into the component
  • You render on the server and want a package whose README explicitly describes it as server-rendering friendly
Skip it if

Setup reality

Install with `npm i use-debounce`. Version 10.1.1 has no runtime dependencies, ships its own TypeScript declarations, declares React as a peer with an unrestricted version range, and declares Node 16 or newer for the package tooling/runtime environment. There is no provider or configuration file. The work is choosing semantics the package cannot choose for you. `useDebounce(value, delay)` compares the previous and next value with strict reference equality by default, so recreating arrays or objects on every render continuously starts new waits; provide `equalityFn`, memoize the value, or debounce the callback instead. Event handlers should pass the needed primitive such as `event.target.value` into the debounced function rather than retaining a framework event object. Decide what unmount means: call `cancel()` when pending work should disappear, or `flush()` when an autosave must run before teardown. Both choices have product consequences. Leading and trailing may both fire during a burst, while `maxWait` forces eventual execution during continuous input. Debounced async calls still need cancellation and stale-response protection because this library only schedules invocation. In tests, use fake timers and wrap timer advancement in React's `act`; otherwise assertions race the clock. Native listeners must remove the exact debounced function reference. The hook is server-rendering friendly, but any callback that reads `window` or DOM state must still run only in the browser. Finally, a call made before the underlying callback has executed returns undefined; later calls return the last invocation result, which is rarely a useful contract for async UI work.

Patterns

Delay a changing search valuedebounce-value

import {useState} from 'react';
import {useDebounce} from 'use-debounce';

function SearchBox() {
  const [query, setQuery] = useState('');
  const [settledQuery] = useDebounce(query, 400);

  return <input value={query} onChange={(event) => setQuery(event.target.value)} />;
}

The rendered input should use the immediate value; use the delayed value for expensive work such as a query effect.

Debounce an autosave callbackdebounce-callback

import {useDebouncedCallback} from 'use-debounce';

const saveDraft = useDebouncedCallback(
  (text: string) => api.saveDraft(text),
  750,
  {maxWait: 5_000},
);

<textarea onChange={(event) => saveDraft(event.target.value)} />

Pass the text value into the callback. maxWait prevents continuous typing from postponing every save indefinitely.

Cancel disposable work during cleanupcancel-on-unmount

import {useEffect} from 'react';

useEffect(() => {
  return () => saveDraft.cancel();
}, [saveDraft]);

Cancel is right for suggestions or analytics you no longer want. For required persistence, flushing may be the product-correct choice.

Flush a required pending saveflush-on-unmount

useEffect(() => {
  return () => {
    if (saveDraft.isPending()) saveDraft.flush();
  };
}, [saveDraft]);

Flush executes during cleanup. Make the callback safe during navigation and do not set state on an unmounted component.

Expose whether work is waitingshow-pending-state

saveDraft(text);
setStatus(saveDraft.isPending() ? 'Waiting to save' : 'Saved');

await Promise.resolve();

isPending reports timer state, not whether an async promise returned by the eventual callback is still in flight.

Run first and last calls in a burstleading-search

const refresh = useDebouncedCallback(loadResults, 500, {
  leading: true,
  trailing: true,
});

With both options enabled, a burst can invoke twice: immediately and again after calls settle. Ensure the operation tolerates that.

Prevent rapid repeat clicksleading-only

const submitOnce = useDebouncedCallback(submitForm, 1_000, {
  leading: true,
  trailing: false,
});

<button onClick={() => submitOnce()}>Submit</button>

Client-side suppression is not a substitute for an idempotency key or server-side duplicate protection.

Debounce an object with explicit equalitycompare-object-values

const [filters] = useDebounce(rawFilters, 300, {
  equalityFn: (previous, next) =>
    previous.query === next.query && previous.sort === next.sort,
});

The default is reference equality, so equivalent object literals restart the timer unless you memoize or supply a comparator.

Throttle a scroll handlerthrottle-scroll

import {useThrottledCallback} from 'use-debounce';

const onScroll = useThrottledCallback(() => {
  setScrollY(window.scrollY);
}, 100);

Throttle permits periodic execution during continuous events; debounce waits for a quiet period. Choose based on the UI behavior you need.

Use a stable native event listenernative-event-listener

useEffect(() => {
  window.addEventListener('resize', onResize);
  return () => {
    window.removeEventListener('resize', onResize);
    onResize.cancel();
  };
}, [onResize]);

Removal must receive the same debounced function reference that was registered, and cancellation clears any trailing invocation.

Abort the previous debounced requestabort-stale-request

const controllerRef = useRef<AbortController>();
const search = useDebouncedCallback(async (query: string) => {
  controllerRef.current?.abort();
  const controller = new AbortController();
  controllerRef.current = controller;
  const result = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {signal: controller.signal});
  setResults(await result.json());
}, 300);

Debouncing reduces starts but does not order network completions. Aborting the prior request prevents an older response from replacing newer data.

Test a debounced callback deterministicallytest-with-fake-timers

vi.useFakeTimers();
const callback = vi.fn();
const {result} = renderHook(() => useDebouncedCallback(callback, 500));

act(() => result.current('value'));
expect(callback).not.toHaveBeenCalled();
act(() => vi.advanceTimersByTime(500));
expect(callback).toHaveBeenCalledWith('value');

Advance fake timers inside React act so state and effects settle before assertions. Restore real timers after the test.

Alternatives

PackageRegistryPick it when
lodashnpmYour bundle already includes Lodash and you prefer composing its debounce function with useMemo and cleanup yourself
react-usenpmYou want debouncing as one part of a broad collection of React utility hooks
ahooksnpmYou want a large hooks toolkit with debounced values, functions, requests, and related lifecycle helpers