@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.
Files changed (2) hide show
  1. package/README.md +469 -0
  2. 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.8",
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.8"
20
+ "@usefy/use-debounce-callback": "0.0.11"
21
21
  },
22
22
  "peerDependencies": {
23
23
  "react": "^18.0.0 || ^19.0.0"