Skip to main content
Creates a debounced version of a callback with optional leading behavior, plus imperative flush and cancel controls.

Signature

function useDebouncedCallback<T extends (...args: unknown[]) => unknown>(
  callback: T,
  options: number | UseDebouncedCallbackOptions
): UseDebouncedCallbackReturnValue<T>

Parameters

callback
T extends (...args: unknown[]) => unknown
required
Function to debounce.
options
number | UseDebouncedCallbackOptions
required
Debounce delay in milliseconds, or an options object with delay, leading, and flushOnUnmount.

Returns

debouncedCallback
UseDebouncedCallbackReturnValue<T>
A debounced version of the callback with additional methods.

Usage

Basic debouncing

import { useDebouncedCallback } from '@kuzenbo/hooks';
import { useState } from 'react';

function SearchInput() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const search = useDebouncedCallback(
    async (value: string) => {
      const data = await fetch(`/api/search?q=${value}`).then(r => r.json());
      setResults(data);
    },
    500
  );

  return (
    <input
      value={query}
      onChange={(e) => {
        setQuery(e.target.value);
        search(e.target.value);
      }}
      placeholder="Search..."
    />
  );
}

Leading edge execution

import { useDebouncedCallback } from '@kuzenbo/hooks';

function RateLimitedButton() {
  const handleClick = useDebouncedCallback(
    () => {
      console.log('Button clicked');
    },
    { delay: 1000, leading: true }
  );

  return <button onClick={handleClick}>Click me</button>;
}

Manual flush and cancel

import { useDebouncedCallback } from '@kuzenbo/hooks';
import { useState } from 'react';

function Form() {
  const [value, setValue] = useState('');

  const save = useDebouncedCallback(
    (data: string) => {
      console.log('Saving:', data);
    },
    2000
  );

  return (
    <div>
      <input
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
          save(e.target.value);
        }}
      />
      <button onClick={() => save.flush()}>Save now</button>
      <button onClick={() => save.cancel()}>Cancel</button>
    </div>
  );
}

Flush on unmount

import { useDebouncedCallback } from '@kuzenbo/hooks';
import { useEffect } from 'react';

function Analytics() {
  const trackEvent = useDebouncedCallback(
    (event: string) => {
      fetch('/api/analytics', {
        method: 'POST',
        body: JSON.stringify({ event })
      });
    },
    { delay: 1000, flushOnUnmount: true }
  );

  useEffect(() => {
    trackEvent('page_view');
  }, []);

  return <div>Content</div>;
}

Build docs developers (and LLMs) love