@usefy/use-throttle
v0.2.4
Published
A React hook for throttling values
Maintainers
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-throttlePeer 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.
