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

v0.2.4

Published

A React hook for throttling values

Readme


Overview

@usefy/use-throttle limits value updates to at most once per specified interval, making it perfect for scroll events, resize handlers, mouse movements, and any high-frequency updates that need rate-limiting. Unlike debounce, throttle guarantees regular updates during continuous changes.

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

Why use-throttle?

  • Zero Dependencies — Pure React implementation (uses @usefy/use-debounce internally)
  • TypeScript First — Full type safety with generics and exported interfaces
  • Flexible Options — Leading edge and trailing edge support
  • Guaranteed Updates — Regular updates during continuous changes (unlike debounce)
  • SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
  • Lightweight — Minimal bundle footprint (~200B minified + gzipped)
  • Well Tested — Comprehensive test coverage with Vitest

Throttle vs Debounce

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


Installation

# npm
npm install @usefy/use-throttle

# yarn
yarn add @usefy/use-throttle

# pnpm
pnpm add @usefy/use-throttle

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 internally with maxWait set to achieve throttle behavior.


Quick Start

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

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

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

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

API Reference

useThrottle<T>(value, delay?, options?)

A hook that returns a throttled version of the provided value, limiting updates to at most once per interval.

Parameters

| Parameter | Type | Default | Description | | --------- | -------------------- | ------- | ------------------------------------- | | value | T | — | The value to throttle | | delay | number | 500 | The throttle interval in milliseconds | | options | UseThrottleOptions | {} | Additional configuration options |

Options

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

Returns

| Type | Description | | ---- | ------------------- | | T | The throttled value |


Examples

Scroll Position Tracking

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

function ScrollProgress() {
  const [scrollY, setScrollY] = useState(0);
  const throttledScrollY = useThrottle(scrollY, 100);

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

  const progress = Math.min(
    (throttledScrollY / (document.body.scrollHeight - window.innerHeight)) *
      100,
    100
  );

  return <div className="progress-bar" style={{ width: `${progress}%` }} />;
}

Mouse Position Tracker

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

function MouseTracker() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const throttledPosition = useThrottle(position, 50);

  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener("mousemove", handleMouseMove);
    return () => window.removeEventListener("mousemove", handleMouseMove);
  }, []);

  return (
    <div
      className="cursor-follower"
      style={{
        transform: `translate(${throttledPosition.x}px, ${throttledPosition.y}px)`,
      }}
    />
  );
}

Window Resize Handler

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

function ResponsiveLayout() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  const throttledSize = useThrottle(windowSize, 200);

  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  const layout = throttledSize.width >= 768 ? "desktop" : "mobile";

  return (
    <div className={`layout-${layout}`}>
      Window: {throttledSize.width} x {throttledSize.height}
    </div>
  );
}

Input Value with Frequent Updates

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

function RangeSlider() {
  const [value, setValue] = useState(50);
  const throttledValue = useThrottle(value, 200);

  // API call only happens at most every 200ms
  useEffect(() => {
    updateServerValue(throttledValue);
  }, [throttledValue]);

  return (
    <div>
      <input
        type="range"
        min="0"
        max="100"
        value={value}
        onChange={(e) => setValue(+e.target.value)}
      />
      <span>Value: {throttledValue}</span>
    </div>
  );
}

Leading Edge Only

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

function InstantFeedback() {
  const [clicks, setClicks] = useState(0);

  // Update immediately on first click, ignore subsequent clicks for 500ms
  const throttledClicks = useThrottle(clicks, 500, {
    leading: true,
    trailing: false,
  });

  return (
    <button onClick={() => setClicks((c) => c + 1)}>
      Clicks: {throttledClicks}
    </button>
  );
}

Trailing Edge Only

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

function DelayedUpdate() {
  const [value, setValue] = useState("");

  // Only update after the interval passes
  const throttledValue = useThrottle(value, 300, {
    leading: false,
    trailing: true,
  });

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>Throttled: {throttledValue}</p>
    </div>
  );
}

TypeScript

This hook is written in TypeScript with full generic support.

import { useThrottle, type UseThrottleOptions } from "@usefy/use-throttle";

// Generic type inference
const throttledString = useThrottle("hello", 300); // string
const throttledNumber = useThrottle(42, 300); // number
const throttledObject = useThrottle({ x: 1 }, 300); // { x: number }

// Options type
const options: UseThrottleOptions = {
  leading: true,
  trailing: false,
};

Testing

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

Test Coverage

📊 View Detailed Coverage Report (GitHub Pages)

Test Categories

  • Initialize with string, number, boolean, object, array values
  • Use default delay of 500ms when not specified
  • Update immediately on first change (leading edge)
  • Throttle rapid updates after leading edge
  • Update at most once per interval during continuous changes
  • Allow new leading edge after interval passes
  • Update immediately with leading: true (default)
  • No immediate update with leading: false
  • Update on trailing edge with trailing: true (default)

License

MIT © mirunamu

This package is part of the usefy monorepo.