@usefy/use-throttle-callback 0.0.8 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +469 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-throttle-callback</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A powerful React hook for throttled callbacks with cancel, flush, and pending methods</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-throttle-callback">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-throttle-callback.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-throttle-callback">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-throttle-callback.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-throttle-callback">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-throttle-callback?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-throttle-callback.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
|
|
38
|
+
`@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.
|
|
39
|
+
|
|
40
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
41
|
+
|
|
42
|
+
### Why use-throttle-callback?
|
|
43
|
+
|
|
44
|
+
- **Zero Dependencies** — Pure React implementation (uses @usefy/use-debounce-callback internally)
|
|
45
|
+
- **TypeScript First** — Full type safety with generics and exported interfaces
|
|
46
|
+
- **Full Control** — `cancel()`, `flush()`, and `pending()` methods
|
|
47
|
+
- **Flexible Options** — Leading edge and trailing edge support
|
|
48
|
+
- **Guaranteed Execution** — Regular invocations during continuous calls (unlike debounce)
|
|
49
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
50
|
+
- **Lightweight** — Minimal bundle footprint (~200B minified + gzipped)
|
|
51
|
+
|
|
52
|
+
### Throttle vs Debounce Callbacks
|
|
53
|
+
|
|
54
|
+
| Feature | Throttle Callback | Debounce Callback |
|
|
55
|
+
|---------|-------------------|-------------------|
|
|
56
|
+
| First invocation | Immediate (leading: true) | After delay |
|
|
57
|
+
| During rapid calls | Regular intervals | Waits for pause |
|
|
58
|
+
| Best for | Scroll, resize, mouse move | Search, form validation |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# npm
|
|
66
|
+
npm install @usefy/use-throttle-callback
|
|
67
|
+
|
|
68
|
+
# yarn
|
|
69
|
+
yarn add @usefy/use-throttle-callback
|
|
70
|
+
|
|
71
|
+
# pnpm
|
|
72
|
+
pnpm add @usefy/use-throttle-callback
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Peer Dependencies
|
|
76
|
+
|
|
77
|
+
This package requires React 18 or 19:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"peerDependencies": {
|
|
82
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Internal Dependencies
|
|
88
|
+
|
|
89
|
+
This package uses [@usefy/use-debounce-callback](https://www.npmjs.com/package/@usefy/use-debounce-callback) internally with `maxWait` set to achieve throttle behavior.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Quick Start
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
97
|
+
|
|
98
|
+
function ScrollTracker() {
|
|
99
|
+
const handleScroll = useThrottleCallback(() => {
|
|
100
|
+
console.log('Scroll position:', window.scrollY);
|
|
101
|
+
}, 100);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
window.addEventListener('scroll', handleScroll);
|
|
105
|
+
return () => {
|
|
106
|
+
handleScroll.cancel();
|
|
107
|
+
window.removeEventListener('scroll', handleScroll);
|
|
108
|
+
};
|
|
109
|
+
}, [handleScroll]);
|
|
110
|
+
|
|
111
|
+
return <div>Scroll to see throttled logs</div>;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## API Reference
|
|
118
|
+
|
|
119
|
+
### `useThrottleCallback<T>(callback, delay?, options?)`
|
|
120
|
+
|
|
121
|
+
A hook that returns a throttled version of the provided callback function.
|
|
122
|
+
|
|
123
|
+
#### Parameters
|
|
124
|
+
|
|
125
|
+
| Parameter | Type | Default | Description |
|
|
126
|
+
|-----------|------|---------|-------------|
|
|
127
|
+
| `callback` | `T extends (...args: any[]) => any` | — | The callback function to throttle |
|
|
128
|
+
| `delay` | `number` | `500` | The throttle interval in milliseconds |
|
|
129
|
+
| `options` | `UseThrottleCallbackOptions` | `{}` | Additional configuration options |
|
|
130
|
+
|
|
131
|
+
#### Options
|
|
132
|
+
|
|
133
|
+
| Option | Type | Default | Description |
|
|
134
|
+
|--------|------|---------|-------------|
|
|
135
|
+
| `leading` | `boolean` | `true` | Invoke on the leading edge (first call) |
|
|
136
|
+
| `trailing` | `boolean` | `true` | Invoke on the trailing edge (after interval) |
|
|
137
|
+
|
|
138
|
+
#### Returns `ThrottledFunction<T>`
|
|
139
|
+
|
|
140
|
+
| Property | Type | Description |
|
|
141
|
+
|----------|------|-------------|
|
|
142
|
+
| `(...args)` | `ReturnType<T>` | The throttled function (same signature as original) |
|
|
143
|
+
| `cancel` | `() => void` | Cancels any pending invocation |
|
|
144
|
+
| `flush` | `() => void` | Immediately invokes any pending invocation |
|
|
145
|
+
| `pending` | `() => boolean` | Returns `true` if there's a pending invocation |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Examples
|
|
150
|
+
|
|
151
|
+
### Scroll Event Handler
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
155
|
+
|
|
156
|
+
function InfiniteScroll() {
|
|
157
|
+
const [items, setItems] = useState([]);
|
|
158
|
+
|
|
159
|
+
const handleScroll = useThrottleCallback(() => {
|
|
160
|
+
const scrollTop = window.scrollY;
|
|
161
|
+
const windowHeight = window.innerHeight;
|
|
162
|
+
const docHeight = document.documentElement.scrollHeight;
|
|
163
|
+
|
|
164
|
+
if (scrollTop + windowHeight >= docHeight - 100) {
|
|
165
|
+
loadMoreItems();
|
|
166
|
+
}
|
|
167
|
+
}, 200);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
171
|
+
return () => {
|
|
172
|
+
handleScroll.cancel();
|
|
173
|
+
window.removeEventListener('scroll', handleScroll);
|
|
174
|
+
};
|
|
175
|
+
}, [handleScroll]);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div>
|
|
179
|
+
{items.map(item => <ItemCard key={item.id} item={item} />)}
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Mouse Movement Tracking
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
189
|
+
|
|
190
|
+
function HeatmapTracker() {
|
|
191
|
+
const recordPosition = useThrottleCallback((x: number, y: number) => {
|
|
192
|
+
analytics.recordMousePosition({ x, y, timestamp: Date.now() });
|
|
193
|
+
}, 50);
|
|
194
|
+
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
197
|
+
recordPosition(e.clientX, e.clientY);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
201
|
+
return () => {
|
|
202
|
+
recordPosition.cancel();
|
|
203
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
204
|
+
};
|
|
205
|
+
}, [recordPosition]);
|
|
206
|
+
|
|
207
|
+
return <div>Tracking mouse movement...</div>;
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Window Resize Handler
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
215
|
+
|
|
216
|
+
function ResponsiveChart() {
|
|
217
|
+
const [dimensions, setDimensions] = useState({ width: 800, height: 400 });
|
|
218
|
+
|
|
219
|
+
const handleResize = useThrottleCallback(() => {
|
|
220
|
+
const container = document.getElementById('chart-container');
|
|
221
|
+
if (container) {
|
|
222
|
+
setDimensions({
|
|
223
|
+
width: container.clientWidth,
|
|
224
|
+
height: container.clientHeight,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}, 150);
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
window.addEventListener('resize', handleResize);
|
|
231
|
+
return () => {
|
|
232
|
+
handleResize.cancel();
|
|
233
|
+
window.removeEventListener('resize', handleResize);
|
|
234
|
+
};
|
|
235
|
+
}, [handleResize]);
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div id="chart-container">
|
|
239
|
+
<Chart width={dimensions.width} height={dimensions.height} />
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Drag Handler with Flush
|
|
246
|
+
|
|
247
|
+
```tsx
|
|
248
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
249
|
+
|
|
250
|
+
function Draggable() {
|
|
251
|
+
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
252
|
+
|
|
253
|
+
const updatePosition = useThrottleCallback((x: number, y: number) => {
|
|
254
|
+
setPosition({ x, y });
|
|
255
|
+
syncPositionToServer({ x, y });
|
|
256
|
+
}, 100);
|
|
257
|
+
|
|
258
|
+
const handleDrag = (e: DragEvent) => {
|
|
259
|
+
updatePosition(e.clientX, e.clientY);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const handleDragEnd = () => {
|
|
263
|
+
// Ensure final position is synced
|
|
264
|
+
updatePosition.flush();
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div
|
|
269
|
+
draggable
|
|
270
|
+
onDrag={handleDrag}
|
|
271
|
+
onDragEnd={handleDragEnd}
|
|
272
|
+
style={{ transform: `translate(${position.x}px, ${position.y}px)` }}
|
|
273
|
+
>
|
|
274
|
+
Drag me
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Real-time Input Sync
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
284
|
+
|
|
285
|
+
function CollaborativeEditor() {
|
|
286
|
+
const [content, setContent] = useState('');
|
|
287
|
+
|
|
288
|
+
const syncToServer = useThrottleCallback((text: string) => {
|
|
289
|
+
webSocket.send(JSON.stringify({ type: 'update', content: text }));
|
|
290
|
+
}, 200);
|
|
291
|
+
|
|
292
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
293
|
+
const newContent = e.target.value;
|
|
294
|
+
setContent(newContent);
|
|
295
|
+
syncToServer(newContent);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// Ensure sync on unmount
|
|
299
|
+
useEffect(() => {
|
|
300
|
+
return () => {
|
|
301
|
+
syncToServer.flush();
|
|
302
|
+
};
|
|
303
|
+
}, [syncToServer]);
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<textarea
|
|
307
|
+
value={content}
|
|
308
|
+
onChange={handleChange}
|
|
309
|
+
placeholder="Start typing..."
|
|
310
|
+
/>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Leading Edge Only (Immediate Response)
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
319
|
+
|
|
320
|
+
function ButtonWithCooldown() {
|
|
321
|
+
const [clickCount, setClickCount] = useState(0);
|
|
322
|
+
|
|
323
|
+
// First click is immediate, then 1 second cooldown
|
|
324
|
+
const handleClick = useThrottleCallback(
|
|
325
|
+
() => {
|
|
326
|
+
setClickCount(c => c + 1);
|
|
327
|
+
},
|
|
328
|
+
1000,
|
|
329
|
+
{ leading: true, trailing: false }
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return (
|
|
333
|
+
<button onClick={handleClick}>
|
|
334
|
+
Clicked {clickCount} times (1s cooldown)
|
|
335
|
+
</button>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Pending State Indicator
|
|
341
|
+
|
|
342
|
+
```tsx
|
|
343
|
+
import { useThrottleCallback } from '@usefy/use-throttle-callback';
|
|
344
|
+
|
|
345
|
+
function SaveIndicator() {
|
|
346
|
+
const [data, setData] = useState({});
|
|
347
|
+
|
|
348
|
+
const save = useThrottleCallback((newData: object) => {
|
|
349
|
+
fetch('/api/save', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
body: JSON.stringify(newData),
|
|
352
|
+
});
|
|
353
|
+
}, 500);
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<div>
|
|
357
|
+
<button onClick={() => save(data)}>
|
|
358
|
+
Save
|
|
359
|
+
</button>
|
|
360
|
+
{save.pending() && <span className="saving-indicator">Saving...</span>}
|
|
361
|
+
</div>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## TypeScript
|
|
369
|
+
|
|
370
|
+
This hook is written in TypeScript with full generic support.
|
|
371
|
+
|
|
372
|
+
```tsx
|
|
373
|
+
import {
|
|
374
|
+
useThrottleCallback,
|
|
375
|
+
type UseThrottleCallbackOptions,
|
|
376
|
+
type ThrottledFunction,
|
|
377
|
+
} from '@usefy/use-throttle-callback';
|
|
378
|
+
|
|
379
|
+
// Type inference from callback
|
|
380
|
+
const throttledFn = useThrottleCallback((x: number, y: number) => {
|
|
381
|
+
return { x, y };
|
|
382
|
+
}, 100);
|
|
383
|
+
|
|
384
|
+
// throttledFn(number, number) => { x: number, y: number } | undefined
|
|
385
|
+
// throttledFn.cancel() => void
|
|
386
|
+
// throttledFn.flush() => void
|
|
387
|
+
// throttledFn.pending() => boolean
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Testing
|
|
393
|
+
|
|
394
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
395
|
+
|
|
396
|
+
### Test Coverage
|
|
397
|
+
|
|
398
|
+
| Category | Tests | Coverage |
|
|
399
|
+
|----------|-------|----------|
|
|
400
|
+
| Initialization | 3 | 100% |
|
|
401
|
+
| Basic Throttling | 4 | 100% |
|
|
402
|
+
| Leading Edge | 3 | 100% |
|
|
403
|
+
| Trailing Edge | 2 | 100% |
|
|
404
|
+
| Control Methods | 4 | 100% |
|
|
405
|
+
| Callback Updates | 2 | 100% |
|
|
406
|
+
| Cleanup | 2 | 100% |
|
|
407
|
+
| **Total** | **20** | **100%** |
|
|
408
|
+
|
|
409
|
+
### Running Tests
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
# Run all tests
|
|
413
|
+
pnpm test
|
|
414
|
+
|
|
415
|
+
# Run tests in watch mode
|
|
416
|
+
pnpm test:watch
|
|
417
|
+
|
|
418
|
+
# Run tests with coverage report
|
|
419
|
+
pnpm test --coverage
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Related Packages
|
|
425
|
+
|
|
426
|
+
Explore other hooks in the **@usefy** collection:
|
|
427
|
+
|
|
428
|
+
| Package | Description |
|
|
429
|
+
|---------|-------------|
|
|
430
|
+
| [@usefy/use-throttle](https://www.npmjs.com/package/@usefy/use-throttle) | Value throttling |
|
|
431
|
+
| [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
|
|
432
|
+
| [@usefy/use-debounce-callback](https://www.npmjs.com/package/@usefy/use-debounce-callback) | Debounced callbacks |
|
|
433
|
+
| [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
|
|
434
|
+
| [@usefy/use-counter](https://www.npmjs.com/package/@usefy/use-counter) | Counter state management |
|
|
435
|
+
| [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Contributing
|
|
440
|
+
|
|
441
|
+
We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# Clone the repository
|
|
445
|
+
git clone https://github.com/geon0529/usefy.git
|
|
446
|
+
|
|
447
|
+
# Install dependencies
|
|
448
|
+
pnpm install
|
|
449
|
+
|
|
450
|
+
# Run tests
|
|
451
|
+
pnpm test
|
|
452
|
+
|
|
453
|
+
# Build
|
|
454
|
+
pnpm build
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## License
|
|
460
|
+
|
|
461
|
+
MIT © [mirunamu](https://github.com/geon0529)
|
|
462
|
+
|
|
463
|
+
This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
<p align="center">
|
|
468
|
+
<sub>Built with care by the usefy team</sub>
|
|
469
|
+
</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefy/use-throttle-callback",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "A React hook for throttling callback functions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"sideEffects": false,
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@usefy/use-debounce-callback": "0.0.
|
|
20
|
+
"@usefy/use-debounce-callback": "0.0.11"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": "^18.0.0 || ^19.0.0"
|