npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@usefy/use-throttle-callback

v0.2.5

Published

A React hook for throttling callback functions

Readme


Overview

@usefy/use-throttle-callback provides a throttled version of your callback function that limits invocations to at most once per specified interval. Perfect for scroll handlers, resize events, mouse movement tracking, and any high-frequency event that needs rate-limiting with full control methods.

Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.

Why use-throttle-callback?

  • Zero Dependencies — Pure React implementation (uses @usefy/use-debounce-callback internally)
  • TypeScript First — Full type safety with generics and exported interfaces
  • Full Controlcancel(), flush(), and pending() methods
  • Flexible Options — Leading edge and trailing edge support
  • Guaranteed Execution — Regular invocations during continuous calls (unlike debounce)
  • SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
  • Lightweight — Minimal bundle footprint (~200B minified + gzipped)

Throttle vs Debounce Callbacks

| Feature | Throttle Callback | Debounce Callback | | ------------------ | -------------------------- | ----------------------- | | First invocation | Immediate (leading: true) | After delay | | During rapid calls | Regular intervals | Waits for pause | | Best for | Scroll, resize, mouse move | Search, form validation |


Installation

# npm
npm install @usefy/use-throttle-callback

# yarn
yarn add @usefy/use-throttle-callback

# pnpm
pnpm add @usefy/use-throttle-callback

Peer Dependencies

This package requires React 18 or 19:

{
  "peerDependencies": {
    "react": "^18.0.0 || ^19.0.0"
  }
}

Internal Dependencies

This package uses @usefy/use-debounce-callback internally with maxWait set to achieve throttle behavior.


Quick Start

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function ScrollTracker() {
  const handleScroll = useThrottleCallback(() => {
    console.log("Scroll position:", window.scrollY);
  }, 100);

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

  return <div>Scroll to see throttled logs</div>;
}

API Reference

useThrottleCallback<T>(callback, delay?, options?)

A hook that returns a throttled version of the provided callback function.

Parameters

| Parameter | Type | Default | Description | | ---------- | ----------------------------------- | ------- | ------------------------------------- | | callback | T extends (...args: any[]) => any | — | The callback function to throttle | | delay | number | 500 | The throttle interval in milliseconds | | options | UseThrottleCallbackOptions | {} | Additional configuration options |

Options

| Option | Type | Default | Description | | ---------- | --------- | ------- | -------------------------------------------- | | leading | boolean | true | Invoke on the leading edge (first call) | | trailing | boolean | true | Invoke on the trailing edge (after interval) |

Returns ThrottledFunction<T>

| Property | Type | Description | | ----------- | --------------- | --------------------------------------------------- | | (...args) | ReturnType<T> | The throttled function (same signature as original) | | cancel | () => void | Cancels any pending invocation | | flush | () => void | Immediately invokes any pending invocation | | pending | () => boolean | Returns true if there's a pending invocation |


Examples

Scroll Event Handler

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function InfiniteScroll() {
  const [items, setItems] = useState([]);

  const handleScroll = useThrottleCallback(() => {
    const scrollTop = window.scrollY;
    const windowHeight = window.innerHeight;
    const docHeight = document.documentElement.scrollHeight;

    if (scrollTop + windowHeight >= docHeight - 100) {
      loadMoreItems();
    }
  }, 200);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => {
      handleScroll.cancel();
      window.removeEventListener("scroll", handleScroll);
    };
  }, [handleScroll]);

  return (
    <div>
      {items.map((item) => (
        <ItemCard key={item.id} item={item} />
      ))}
    </div>
  );
}

Mouse Movement Tracking

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function HeatmapTracker() {
  const recordPosition = useThrottleCallback((x: number, y: number) => {
    analytics.recordMousePosition({ x, y, timestamp: Date.now() });
  }, 50);

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      recordPosition(e.clientX, e.clientY);
    };

    document.addEventListener("mousemove", handleMouseMove);
    return () => {
      recordPosition.cancel();
      document.removeEventListener("mousemove", handleMouseMove);
    };
  }, [recordPosition]);

  return <div>Tracking mouse movement...</div>;
}

Window Resize Handler

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function ResponsiveChart() {
  const [dimensions, setDimensions] = useState({ width: 800, height: 400 });

  const handleResize = useThrottleCallback(() => {
    const container = document.getElementById("chart-container");
    if (container) {
      setDimensions({
        width: container.clientWidth,
        height: container.clientHeight,
      });
    }
  }, 150);

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

  return (
    <div id="chart-container">
      <Chart width={dimensions.width} height={dimensions.height} />
    </div>
  );
}

Drag Handler with Flush

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function Draggable() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const updatePosition = useThrottleCallback((x: number, y: number) => {
    setPosition({ x, y });
    syncPositionToServer({ x, y });
  }, 100);

  const handleDrag = (e: DragEvent) => {
    updatePosition(e.clientX, e.clientY);
  };

  const handleDragEnd = () => {
    // Ensure final position is synced
    updatePosition.flush();
  };

  return (
    <div
      draggable
      onDrag={handleDrag}
      onDragEnd={handleDragEnd}
      style={{ transform: `translate(${position.x}px, ${position.y}px)` }}
    >
      Drag me
    </div>
  );
}

Real-time Input Sync

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function CollaborativeEditor() {
  const [content, setContent] = useState("");

  const syncToServer = useThrottleCallback((text: string) => {
    webSocket.send(JSON.stringify({ type: "update", content: text }));
  }, 200);

  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const newContent = e.target.value;
    setContent(newContent);
    syncToServer(newContent);
  };

  // Ensure sync on unmount
  useEffect(() => {
    return () => {
      syncToServer.flush();
    };
  }, [syncToServer]);

  return (
    <textarea
      value={content}
      onChange={handleChange}
      placeholder="Start typing..."
    />
  );
}

Leading Edge Only (Immediate Response)

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function ButtonWithCooldown() {
  const [clickCount, setClickCount] = useState(0);

  // First click is immediate, then 1 second cooldown
  const handleClick = useThrottleCallback(
    () => {
      setClickCount((c) => c + 1);
    },
    1000,
    { leading: true, trailing: false }
  );

  return (
    <button onClick={handleClick}>
      Clicked {clickCount} times (1s cooldown)
    </button>
  );
}

Pending State Indicator

import { useThrottleCallback } from "@usefy/use-throttle-callback";

function SaveIndicator() {
  const [data, setData] = useState({});

  const save = useThrottleCallback((newData: object) => {
    fetch("/api/save", {
      method: "POST",
      body: JSON.stringify(newData),
    });
  }, 500);

  return (
    <div>
      <button onClick={() => save(data)}>Save</button>
      {save.pending() && <span className="saving-indicator">Saving...</span>}
    </div>
  );
}

TypeScript

This hook is written in TypeScript with full generic support.

import {
  useThrottleCallback,
  type UseThrottleCallbackOptions,
  type ThrottledFunction,
} from "@usefy/use-throttle-callback";

// Type inference from callback
const throttledFn = useThrottleCallback((x: number, y: number) => {
  return { x, y };
}, 100);

// throttledFn(number, number) => { x: number, y: number } | undefined
// throttledFn.cancel() => void
// throttledFn.flush() => void
// throttledFn.pending() => boolean

Testing

This package maintains comprehensive test coverage to ensure reliability and stability.

Test Coverage

📊 View Detailed Coverage Report (GitHub Pages)


License

MIT © mirunamu

This package is part of the usefy monorepo.