Skip to main content
Creates a throttled version of a callback. The callback runs immediately on the first call, then at most once per wait window with the latest queued arguments.

Signature

function useThrottledCallback<TArgs extends readonly unknown[]>(
  callback: (...args: TArgs) => void,
  wait: number
): (...args: TArgs) => void

Parameters

callback
(...args: TArgs) => void
required
Function to invoke in a throttled manner.
wait
number
required
Throttle window in milliseconds.

Returns

throttledCallback
(...args: TArgs) => void
A throttled version of the callback that executes immediately on the first call, then at most once per wait period.

Usage

Throttled scroll handler

import { useThrottledCallback } from '@kuzenbo/hooks';
import { useEffect, useState } from 'react';

function ScrollTracker() {
  const [scrollY, setScrollY] = useState(0);

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

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return <div>Scroll position: {scrollY}px</div>;
}

Throttled input handler

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

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

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

  return (
    <div>
      <input
        value={query}
        onChange={(e) => {
          setQuery(e.target.value);
          search(e.target.value);
        }}
        placeholder="Search..."
      />
      <ul>
        {results.map((r, i) => <li key={i}>{r}</li>)}
      </ul>
    </div>
  );
}

Throttled API calls

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

function RateLimitedCounter() {
  const [count, setCount] = useState(0);

  const increment = useThrottledCallback(() => {
    fetch('/api/increment', { method: 'POST' })
      .then(r => r.json())
      .then(data => setCount(data.count));
  }, 1000);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment (max 1/sec)</button>
    </div>
  );
}

Throttled resize handler

import { useThrottledCallback } from '@kuzenbo/hooks';
import { useEffect, useState } from 'react';

function ResponsiveLayout() {
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  const handleResize = useThrottledCallback(() => {
    setDimensions({
      width: window.innerWidth,
      height: window.innerHeight
    });
  }, 200);

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

  return (
    <div>
      <p>Window: {dimensions.width} x {dimensions.height}</p>
    </div>
  );
}

Build docs developers (and LLMs) love