@usefy/use-debounce-callback 0.0.7 → 0.0.10

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 ADDED
@@ -0,0 +1,476 @@
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-debounce-callback</h1>
6
+
7
+ <p align="center">
8
+ <strong>A powerful React hook for debounced 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-debounce-callback">
13
+ <img src="https://img.shields.io/npm/v/@usefy/use-debounce-callback.svg?style=flat-square&color=007acc" alt="npm version" />
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@usefy/use-debounce-callback">
16
+ <img src="https://img.shields.io/npm/dm/@usefy/use-debounce-callback.svg?style=flat-square&color=007acc" alt="npm downloads" />
17
+ </a>
18
+ <a href="https://bundlephobia.com/package/@usefy/use-debounce-callback">
19
+ <img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-debounce-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-debounce-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-debounce-callback` provides a debounced version of your callback function with full control methods: `cancel()`, `flush()`, and `pending()`. Perfect for API calls, form submissions, event handlers, and any scenario requiring debounced function execution with fine-grained control.
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-debounce-callback?
43
+
44
+ - **Zero Dependencies** — Pure React implementation with no external dependencies
45
+ - **TypeScript First** — Full type safety with generics and exported interfaces
46
+ - **Full Control** — `cancel()`, `flush()`, and `pending()` methods
47
+ - **Flexible Options** — Leading edge, trailing edge, and maxWait support
48
+ - **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
49
+ - **Lightweight** — Minimal bundle footprint (~500B minified + gzipped)
50
+ - **Well Tested** — Comprehensive test coverage with Vitest
51
+
52
+ ---
53
+
54
+ ## Installation
55
+
56
+ ```bash
57
+ # npm
58
+ npm install @usefy/use-debounce-callback
59
+
60
+ # yarn
61
+ yarn add @usefy/use-debounce-callback
62
+
63
+ # pnpm
64
+ pnpm add @usefy/use-debounce-callback
65
+ ```
66
+
67
+ ### Peer Dependencies
68
+
69
+ This package requires React 18 or 19:
70
+
71
+ ```json
72
+ {
73
+ "peerDependencies": {
74
+ "react": "^18.0.0 || ^19.0.0"
75
+ }
76
+ }
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Quick Start
82
+
83
+ ```tsx
84
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
85
+
86
+ function SearchInput() {
87
+ const [query, setQuery] = useState('');
88
+
89
+ const debouncedSearch = useDebounceCallback((searchTerm: string) => {
90
+ fetchSearchResults(searchTerm);
91
+ }, 300);
92
+
93
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
94
+ setQuery(e.target.value);
95
+ debouncedSearch(e.target.value);
96
+ };
97
+
98
+ return (
99
+ <input
100
+ type="text"
101
+ value={query}
102
+ onChange={handleChange}
103
+ placeholder="Search..."
104
+ />
105
+ );
106
+ }
107
+ ```
108
+
109
+ ---
110
+
111
+ ## API Reference
112
+
113
+ ### `useDebounceCallback<T>(callback, delay?, options?)`
114
+
115
+ A hook that returns a debounced version of the provided callback function.
116
+
117
+ #### Parameters
118
+
119
+ | Parameter | Type | Default | Description |
120
+ |-----------|------|---------|-------------|
121
+ | `callback` | `T extends (...args: any[]) => any` | — | The callback function to debounce |
122
+ | `delay` | `number` | `500` | The debounce delay in milliseconds |
123
+ | `options` | `UseDebounceCallbackOptions` | `{}` | Additional configuration options |
124
+
125
+ #### Options
126
+
127
+ | Option | Type | Default | Description |
128
+ |--------|------|---------|-------------|
129
+ | `leading` | `boolean` | `false` | Invoke on the leading edge (first call) |
130
+ | `trailing` | `boolean` | `true` | Invoke on the trailing edge (after delay) |
131
+ | `maxWait` | `number` | — | Maximum time to wait before forcing invocation |
132
+
133
+ #### Returns `DebouncedFunction<T>`
134
+
135
+ | Property | Type | Description |
136
+ |----------|------|-------------|
137
+ | `(...args)` | `ReturnType<T>` | The debounced function (same signature as original) |
138
+ | `cancel` | `() => void` | Cancels any pending invocation |
139
+ | `flush` | `() => void` | Immediately invokes any pending invocation |
140
+ | `pending` | `() => boolean` | Returns `true` if there's a pending invocation |
141
+
142
+ ---
143
+
144
+ ## Examples
145
+
146
+ ### Auto-Save with Cancel
147
+
148
+ ```tsx
149
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
150
+
151
+ function Editor() {
152
+ const [content, setContent] = useState('');
153
+
154
+ const debouncedSave = useDebounceCallback((text: string) => {
155
+ saveToServer(text);
156
+ console.log('Auto-saved');
157
+ }, 1000);
158
+
159
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
160
+ setContent(e.target.value);
161
+ debouncedSave(e.target.value);
162
+ };
163
+
164
+ const handleManualSave = () => {
165
+ // Flush any pending save immediately
166
+ debouncedSave.flush();
167
+ };
168
+
169
+ const handleDiscard = () => {
170
+ // Cancel pending save and reset content
171
+ debouncedSave.cancel();
172
+ setContent('');
173
+ };
174
+
175
+ return (
176
+ <div>
177
+ <textarea value={content} onChange={handleChange} />
178
+ <button onClick={handleManualSave}>Save Now</button>
179
+ <button onClick={handleDiscard}>Discard</button>
180
+ {debouncedSave.pending() && <span>Saving...</span>}
181
+ </div>
182
+ );
183
+ }
184
+ ```
185
+
186
+ ### Search with Immediate First Call
187
+
188
+ ```tsx
189
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
190
+
191
+ function SearchWithSuggestions() {
192
+ const [results, setResults] = useState([]);
193
+
194
+ // First keystroke triggers immediate search, then debounce
195
+ const debouncedSearch = useDebounceCallback(
196
+ async (query: string) => {
197
+ const data = await fetch(`/api/search?q=${query}`);
198
+ setResults(await data.json());
199
+ },
200
+ 300,
201
+ { leading: true }
202
+ );
203
+
204
+ return (
205
+ <input
206
+ type="text"
207
+ onChange={(e) => debouncedSearch(e.target.value)}
208
+ placeholder="Search..."
209
+ />
210
+ );
211
+ }
212
+ ```
213
+
214
+ ### Form Validation
215
+
216
+ ```tsx
217
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
218
+
219
+ function RegistrationForm() {
220
+ const [email, setEmail] = useState('');
221
+ const [error, setError] = useState('');
222
+
223
+ const validateEmail = useDebounceCallback(async (value: string) => {
224
+ if (!value.includes('@')) {
225
+ setError('Invalid email format');
226
+ return;
227
+ }
228
+ const response = await fetch(`/api/check-email?e=${value}`);
229
+ const { available } = await response.json();
230
+ setError(available ? '' : 'Email already registered');
231
+ }, 500);
232
+
233
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
234
+ setEmail(e.target.value);
235
+ setError(''); // Clear error immediately
236
+ validateEmail(e.target.value);
237
+ };
238
+
239
+ return (
240
+ <div>
241
+ <input
242
+ type="email"
243
+ value={email}
244
+ onChange={handleChange}
245
+ placeholder="Enter email"
246
+ />
247
+ {error && <span className="error">{error}</span>}
248
+ </div>
249
+ );
250
+ }
251
+ ```
252
+
253
+ ### Event Handler with maxWait
254
+
255
+ ```tsx
256
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
257
+
258
+ function ResizeHandler() {
259
+ const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
260
+
261
+ // Debounce resize events, but guarantee update every 1 second
262
+ const handleResize = useDebounceCallback(
263
+ () => {
264
+ setDimensions({
265
+ width: window.innerWidth,
266
+ height: window.innerHeight,
267
+ });
268
+ },
269
+ 250,
270
+ { maxWait: 1000 }
271
+ );
272
+
273
+ useEffect(() => {
274
+ window.addEventListener('resize', handleResize);
275
+ return () => {
276
+ handleResize.cancel();
277
+ window.removeEventListener('resize', handleResize);
278
+ };
279
+ }, [handleResize]);
280
+
281
+ return (
282
+ <div>
283
+ Window: {dimensions.width} x {dimensions.height}
284
+ </div>
285
+ );
286
+ }
287
+ ```
288
+
289
+ ### API Request with Pending State
290
+
291
+ ```tsx
292
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
293
+
294
+ function DataFetcher() {
295
+ const [data, setData] = useState(null);
296
+ const [loading, setLoading] = useState(false);
297
+
298
+ const fetchData = useDebounceCallback(
299
+ async (params: QueryParams) => {
300
+ setLoading(true);
301
+ try {
302
+ const response = await fetch('/api/data', {
303
+ method: 'POST',
304
+ body: JSON.stringify(params),
305
+ });
306
+ setData(await response.json());
307
+ } finally {
308
+ setLoading(false);
309
+ }
310
+ },
311
+ 500
312
+ );
313
+
314
+ return (
315
+ <div>
316
+ <button onClick={() => fetchData({ page: 1 })}>
317
+ {fetchData.pending() ? 'Request pending...' : 'Fetch Data'}
318
+ </button>
319
+ {loading && <Spinner />}
320
+ </div>
321
+ );
322
+ }
323
+ ```
324
+
325
+ ### Cleanup on Unmount
326
+
327
+ ```tsx
328
+ import { useDebounceCallback } from '@usefy/use-debounce-callback';
329
+
330
+ function Component() {
331
+ const debouncedAction = useDebounceCallback(() => {
332
+ // Some action
333
+ }, 500);
334
+
335
+ // Cancel pending on unmount
336
+ useEffect(() => {
337
+ return () => {
338
+ debouncedAction.cancel();
339
+ };
340
+ }, [debouncedAction]);
341
+
342
+ return <button onClick={debouncedAction}>Action</button>;
343
+ }
344
+ ```
345
+
346
+ ---
347
+
348
+ ## TypeScript
349
+
350
+ This hook is written in TypeScript with full generic support.
351
+
352
+ ```tsx
353
+ import {
354
+ useDebounceCallback,
355
+ type UseDebounceCallbackOptions,
356
+ type DebouncedFunction,
357
+ } from '@usefy/use-debounce-callback';
358
+
359
+ // Type inference from callback
360
+ const debouncedFn = useDebounceCallback((a: string, b: number) => {
361
+ return `${a}-${b}`;
362
+ }, 300);
363
+
364
+ // debouncedFn(string, number) => string | undefined
365
+ // debouncedFn.cancel() => void
366
+ // debouncedFn.flush() => void
367
+ // debouncedFn.pending() => boolean
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Testing
373
+
374
+ This package maintains comprehensive test coverage to ensure reliability and stability.
375
+
376
+ ### Test Coverage
377
+
378
+ | Category | Tests | Coverage |
379
+ |----------|-------|----------|
380
+ | Initialization | 4 | 100% |
381
+ | Basic Debouncing | 6 | 100% |
382
+ | Leading Edge | 5 | 100% |
383
+ | Trailing Edge | 3 | 100% |
384
+ | maxWait Option | 4 | 100% |
385
+ | cancel Method | 4 | 100% |
386
+ | flush Method | 4 | 100% |
387
+ | pending Method | 4 | 100% |
388
+ | Callback Updates | 3 | 100% |
389
+ | Cleanup | 2 | 100% |
390
+ | **Total** | **39** | **94.05%** |
391
+
392
+ ### Test Categories
393
+
394
+ <details>
395
+ <summary><strong>Control Method Tests</strong></summary>
396
+
397
+ - Cancel pending invocations
398
+ - Flush immediately invokes pending callback
399
+ - pending() returns correct state
400
+ - cancel() clears pending state
401
+ - flush() clears pending state after invocation
402
+
403
+ </details>
404
+
405
+ <details>
406
+ <summary><strong>Leading/Trailing Edge Tests</strong></summary>
407
+
408
+ - Invoke on leading edge with leading: true
409
+ - No immediate invoke with leading: false (default)
410
+ - Invoke on trailing edge with trailing: true (default)
411
+ - No trailing invoke with trailing: false
412
+ - Combined leading and trailing options
413
+
414
+ </details>
415
+
416
+ ### Running Tests
417
+
418
+ ```bash
419
+ # Run all tests
420
+ pnpm test
421
+
422
+ # Run tests in watch mode
423
+ pnpm test:watch
424
+
425
+ # Run tests with coverage report
426
+ pnpm test --coverage
427
+ ```
428
+
429
+ ---
430
+
431
+ ## Related Packages
432
+
433
+ Explore other hooks in the **@usefy** collection:
434
+
435
+ | Package | Description |
436
+ |---------|-------------|
437
+ | [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
438
+ | [@usefy/use-throttle](https://www.npmjs.com/package/@usefy/use-throttle) | Value throttling |
439
+ | [@usefy/use-throttle-callback](https://www.npmjs.com/package/@usefy/use-throttle-callback) | Throttled callbacks |
440
+ | [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
441
+ | [@usefy/use-counter](https://www.npmjs.com/package/@usefy/use-counter) | Counter state management |
442
+ | [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
443
+
444
+ ---
445
+
446
+ ## Contributing
447
+
448
+ We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
449
+
450
+ ```bash
451
+ # Clone the repository
452
+ git clone https://github.com/geon0529/usefy.git
453
+
454
+ # Install dependencies
455
+ pnpm install
456
+
457
+ # Run tests
458
+ pnpm test
459
+
460
+ # Build
461
+ pnpm build
462
+ ```
463
+
464
+ ---
465
+
466
+ ## License
467
+
468
+ MIT © [mirunamu](https://github.com/geon0529)
469
+
470
+ This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
471
+
472
+ ---
473
+
474
+ <p align="center">
475
+ <sub>Built with care by the usefy team</sub>
476
+ </p>
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useDebounceCallback.ts"],"sourcesContent":["export {\r\n useDebounceCallback,\r\n type UseDebounceCallbackOptions,\r\n type DebouncedFunction,\r\n} from \"./useDebounceCallback\";\r\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\r\n\r\n/**\r\n * Options for useDebounceCallback hook\r\n */\r\nexport interface UseDebounceCallbackOptions {\r\n /**\r\n * Maximum time the debounced function can be delayed\r\n * @default undefined (no maximum)\r\n */\r\n maxWait?: number;\r\n /**\r\n * Whether to invoke on the leading edge\r\n * @default false\r\n */\r\n leading?: boolean;\r\n /**\r\n * Whether to invoke on the trailing edge\r\n * @default true\r\n */\r\n trailing?: boolean;\r\n}\r\n\r\n/**\r\n * Debounced function interface with control methods\r\n */\r\nexport interface DebouncedFunction<T extends (...args: any[]) => any> {\r\n /**\r\n * Call the debounced function\r\n */\r\n (...args: Parameters<T>): ReturnType<T> | undefined;\r\n /**\r\n * Cancel any pending invocation\r\n */\r\n cancel: () => void;\r\n /**\r\n * Immediately invoke any pending invocation\r\n */\r\n flush: () => ReturnType<T> | undefined;\r\n /**\r\n * Check if there is a pending invocation\r\n */\r\n pending: () => boolean;\r\n}\r\n\r\n/**\r\n * Creates a debounced version of the provided callback function.\r\n * The debounced function delays invoking the callback until after `delay` milliseconds\r\n * have elapsed since the last time the debounced function was invoked.\r\n *\r\n * @template T - The type of the callback function\r\n * @param callback - The function to debounce\r\n * @param delay - The delay in milliseconds (default: 500ms)\r\n * @param options - Additional options for controlling debounce behavior\r\n * @returns A debounced version of the callback with cancel, flush, and pending methods\r\n *\r\n * @example\r\n * ```tsx\r\n * function SearchComponent() {\r\n * const [results, setResults] = useState([]);\r\n *\r\n * const debouncedSearch = useDebounceCallback(\r\n * async (query: string) => {\r\n * const data = await searchAPI(query);\r\n * setResults(data);\r\n * },\r\n * 500\r\n * );\r\n *\r\n * return (\r\n * <input\r\n * type=\"text\"\r\n * onChange={(e) => debouncedSearch(e.target.value)}\r\n * placeholder=\"Search...\"\r\n * />\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With leading edge invocation\r\n * const debouncedFn = useDebounceCallback(callback, 300, { leading: true });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With maximum wait time\r\n * const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });\r\n *\r\n * // Cancel pending invocation\r\n * debouncedFn.cancel();\r\n *\r\n * // Immediately invoke pending invocation\r\n * debouncedFn.flush();\r\n *\r\n * // Check if there's a pending invocation\r\n * if (debouncedFn.pending()) {\r\n * console.log('There is a pending call');\r\n * }\r\n * ```\r\n */\r\nexport function useDebounceCallback<T extends (...args: any[]) => any>(\r\n callback: T,\r\n delay: number = 500,\r\n options: UseDebounceCallbackOptions = {}\r\n): DebouncedFunction<T> {\r\n // Parse options\r\n const wait = delay || 0;\r\n const leading = options.leading ?? false;\r\n const trailing = options.trailing !== undefined ? options.trailing : true;\r\n const maxing = \"maxWait\" in options;\r\n const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : undefined;\r\n\r\n // Refs for mutable state\r\n const callbackRef = useRef<T>(callback);\r\n const timerIdRef = useRef<ReturnType<typeof setTimeout> | undefined>(\r\n undefined\r\n );\r\n const lastCallTimeRef = useRef<number | undefined>(undefined);\r\n const lastInvokeTimeRef = useRef<number>(0);\r\n const lastArgsRef = useRef<Parameters<T> | undefined>(undefined);\r\n const resultRef = useRef<ReturnType<T> | undefined>(undefined);\r\n\r\n // Store options in refs\r\n const waitRef = useRef(wait);\r\n const leadingRef = useRef(leading);\r\n const trailingRef = useRef(trailing);\r\n const maxingRef = useRef(maxing);\r\n const maxWaitRef = useRef(maxWait);\r\n\r\n // Update callback ref on every render to always have the latest callback\r\n callbackRef.current = callback;\r\n\r\n // Update option refs when options change\r\n waitRef.current = wait;\r\n leadingRef.current = leading;\r\n trailingRef.current = trailing;\r\n maxingRef.current = maxing;\r\n maxWaitRef.current = maxWait;\r\n\r\n // Helper function to get current time\r\n const now = useCallback(() => Date.now(), []);\r\n\r\n // Helper function: shouldInvoke\r\n const shouldInvoke = useCallback((time: number): boolean => {\r\n const lastCallTime = lastCallTimeRef.current;\r\n if (lastCallTime === undefined) {\r\n return true; // First call\r\n }\r\n\r\n const timeSinceLastCall = time - lastCallTime;\r\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\r\n\r\n return (\r\n timeSinceLastCall >= waitRef.current ||\r\n timeSinceLastCall < 0 || // System time went backwards\r\n (maxingRef.current &&\r\n timeSinceLastInvoke >= (maxWaitRef.current as number))\r\n );\r\n }, []);\r\n\r\n // Helper function: invokeFunc\r\n const invokeFunc = useCallback((time: number): ReturnType<T> | undefined => {\r\n const args = lastArgsRef.current;\r\n lastArgsRef.current = undefined;\r\n lastInvokeTimeRef.current = time;\r\n\r\n if (args !== undefined) {\r\n resultRef.current = callbackRef.current(...args);\r\n }\r\n return resultRef.current;\r\n }, []);\r\n\r\n // Helper function: remainingWait\r\n const remainingWait = useCallback((time: number): number => {\r\n const lastCallTime = lastCallTimeRef.current;\r\n if (lastCallTime === undefined) {\r\n return waitRef.current;\r\n }\r\n\r\n const timeSinceLastCall = time - lastCallTime;\r\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\r\n const timeWaiting = waitRef.current - timeSinceLastCall;\r\n\r\n return maxingRef.current\r\n ? Math.min(\r\n timeWaiting,\r\n (maxWaitRef.current as number) - timeSinceLastInvoke\r\n )\r\n : timeWaiting;\r\n }, []);\r\n\r\n // Forward declare timerExpired for mutual recursion\r\n const timerExpiredRef = useRef<() => void>(() => {});\r\n\r\n // Helper function: trailingEdge\r\n const trailingEdge = useCallback(\r\n (time: number): ReturnType<T> | undefined => {\r\n timerIdRef.current = undefined;\r\n\r\n // Only invoke if trailing is true and we have args (meaning the function was called)\r\n if (trailingRef.current && lastArgsRef.current !== undefined) {\r\n return invokeFunc(time);\r\n }\r\n lastArgsRef.current = undefined;\r\n return resultRef.current;\r\n },\r\n [invokeFunc]\r\n );\r\n\r\n // Helper function: timerExpired\r\n const timerExpired = useCallback((): void => {\r\n const time = now();\r\n if (shouldInvoke(time)) {\r\n trailingEdge(time);\r\n } else {\r\n // Restart the timer\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n remainingWait(time)\r\n );\r\n }\r\n }, [now, shouldInvoke, trailingEdge, remainingWait]);\r\n\r\n // Update the ref after timerExpired is defined\r\n timerExpiredRef.current = timerExpired;\r\n\r\n // Helper function: leadingEdge\r\n const leadingEdge = useCallback(\r\n (time: number): ReturnType<T> | undefined => {\r\n // Reset any `maxWait` timer\r\n lastInvokeTimeRef.current = time;\r\n // Start the timer for the trailing edge\r\n timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);\r\n // Invoke the leading edge\r\n return leadingRef.current ? invokeFunc(time) : resultRef.current;\r\n },\r\n [invokeFunc]\r\n );\r\n\r\n // Cancel function\r\n const cancel = useCallback((): void => {\r\n if (timerIdRef.current !== undefined) {\r\n clearTimeout(timerIdRef.current);\r\n }\r\n lastInvokeTimeRef.current = 0;\r\n lastArgsRef.current = undefined;\r\n lastCallTimeRef.current = undefined;\r\n timerIdRef.current = undefined;\r\n }, []);\r\n\r\n // Flush function\r\n const flush = useCallback((): ReturnType<T> | undefined => {\r\n if (timerIdRef.current === undefined) {\r\n return resultRef.current;\r\n }\r\n return trailingEdge(now());\r\n }, [now, trailingEdge]);\r\n\r\n // Pending function\r\n const pending = useCallback((): boolean => {\r\n return timerIdRef.current !== undefined;\r\n }, []);\r\n\r\n // Main debounced function\r\n const debounced = useCallback(\r\n (...args: Parameters<T>): ReturnType<T> | undefined => {\r\n const time = now();\r\n const isInvoking = shouldInvoke(time);\r\n\r\n lastArgsRef.current = args;\r\n lastCallTimeRef.current = time;\r\n\r\n if (isInvoking) {\r\n if (timerIdRef.current === undefined) {\r\n return leadingEdge(time);\r\n }\r\n if (maxingRef.current) {\r\n // Handle invocations in a tight loop\r\n clearTimeout(timerIdRef.current);\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n waitRef.current\r\n );\r\n return invokeFunc(time);\r\n }\r\n }\r\n if (timerIdRef.current === undefined) {\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n waitRef.current\r\n );\r\n }\r\n return resultRef.current;\r\n },\r\n [now, shouldInvoke, leadingEdge, invokeFunc]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (timerIdRef.current !== undefined) {\r\n clearTimeout(timerIdRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // Create the debounced function with attached methods\r\n const debouncedWithMethods = useMemo(() => {\r\n const fn = debounced as DebouncedFunction<T>;\r\n fn.cancel = cancel;\r\n fn.flush = flush;\r\n fn.pending = pending;\r\n return fn;\r\n }, [debounced, cancel, flush, pending]);\r\n\r\n return debouncedWithMethods;\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useDebounceCallback.ts"],"sourcesContent":["export {\n useDebounceCallback,\n type UseDebounceCallbackOptions,\n type DebouncedFunction,\n} from \"./useDebounceCallback\";\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\n\n/**\n * Options for useDebounceCallback hook\n */\nexport interface UseDebounceCallbackOptions {\n /**\n * Maximum time the debounced function can be delayed\n * @default undefined (no maximum)\n */\n maxWait?: number;\n /**\n * Whether to invoke on the leading edge\n * @default false\n */\n leading?: boolean;\n /**\n * Whether to invoke on the trailing edge\n * @default true\n */\n trailing?: boolean;\n}\n\n/**\n * Debounced function interface with control methods\n */\nexport interface DebouncedFunction<T extends (...args: any[]) => any> {\n /**\n * Call the debounced function\n */\n (...args: Parameters<T>): ReturnType<T> | undefined;\n /**\n * Cancel any pending invocation\n */\n cancel: () => void;\n /**\n * Immediately invoke any pending invocation\n */\n flush: () => ReturnType<T> | undefined;\n /**\n * Check if there is a pending invocation\n */\n pending: () => boolean;\n}\n\n/**\n * Creates a debounced version of the provided callback function.\n * The debounced function delays invoking the callback until after `delay` milliseconds\n * have elapsed since the last time the debounced function was invoked.\n *\n * @template T - The type of the callback function\n * @param callback - The function to debounce\n * @param delay - The delay in milliseconds (default: 500ms)\n * @param options - Additional options for controlling debounce behavior\n * @returns A debounced version of the callback with cancel, flush, and pending methods\n *\n * @example\n * ```tsx\n * function SearchComponent() {\n * const [results, setResults] = useState([]);\n *\n * const debouncedSearch = useDebounceCallback(\n * async (query: string) => {\n * const data = await searchAPI(query);\n * setResults(data);\n * },\n * 500\n * );\n *\n * return (\n * <input\n * type=\"text\"\n * onChange={(e) => debouncedSearch(e.target.value)}\n * placeholder=\"Search...\"\n * />\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With leading edge invocation\n * const debouncedFn = useDebounceCallback(callback, 300, { leading: true });\n * ```\n *\n * @example\n * ```tsx\n * // With maximum wait time\n * const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });\n *\n * // Cancel pending invocation\n * debouncedFn.cancel();\n *\n * // Immediately invoke pending invocation\n * debouncedFn.flush();\n *\n * // Check if there's a pending invocation\n * if (debouncedFn.pending()) {\n * console.log('There is a pending call');\n * }\n * ```\n */\nexport function useDebounceCallback<T extends (...args: any[]) => any>(\n callback: T,\n delay: number = 500,\n options: UseDebounceCallbackOptions = {}\n): DebouncedFunction<T> {\n // Parse options\n const wait = delay || 0;\n const leading = options.leading ?? false;\n const trailing = options.trailing !== undefined ? options.trailing : true;\n const maxing = \"maxWait\" in options;\n const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : undefined;\n\n // Refs for mutable state\n const callbackRef = useRef<T>(callback);\n const timerIdRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined\n );\n const lastCallTimeRef = useRef<number | undefined>(undefined);\n const lastInvokeTimeRef = useRef<number>(0);\n const lastArgsRef = useRef<Parameters<T> | undefined>(undefined);\n const resultRef = useRef<ReturnType<T> | undefined>(undefined);\n\n // Store options in refs\n const waitRef = useRef(wait);\n const leadingRef = useRef(leading);\n const trailingRef = useRef(trailing);\n const maxingRef = useRef(maxing);\n const maxWaitRef = useRef(maxWait);\n\n // Update callback ref on every render to always have the latest callback\n callbackRef.current = callback;\n\n // Update option refs when options change\n waitRef.current = wait;\n leadingRef.current = leading;\n trailingRef.current = trailing;\n maxingRef.current = maxing;\n maxWaitRef.current = maxWait;\n\n // Helper function to get current time\n const now = useCallback(() => Date.now(), []);\n\n // Helper function: shouldInvoke\n const shouldInvoke = useCallback((time: number): boolean => {\n const lastCallTime = lastCallTimeRef.current;\n if (lastCallTime === undefined) {\n return true; // First call\n }\n\n const timeSinceLastCall = time - lastCallTime;\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\n\n return (\n timeSinceLastCall >= waitRef.current ||\n timeSinceLastCall < 0 || // System time went backwards\n (maxingRef.current &&\n timeSinceLastInvoke >= (maxWaitRef.current as number))\n );\n }, []);\n\n // Helper function: invokeFunc\n const invokeFunc = useCallback((time: number): ReturnType<T> | undefined => {\n const args = lastArgsRef.current;\n lastArgsRef.current = undefined;\n lastInvokeTimeRef.current = time;\n\n if (args !== undefined) {\n resultRef.current = callbackRef.current(...args);\n }\n return resultRef.current;\n }, []);\n\n // Helper function: remainingWait\n const remainingWait = useCallback((time: number): number => {\n const lastCallTime = lastCallTimeRef.current;\n if (lastCallTime === undefined) {\n return waitRef.current;\n }\n\n const timeSinceLastCall = time - lastCallTime;\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\n const timeWaiting = waitRef.current - timeSinceLastCall;\n\n return maxingRef.current\n ? Math.min(\n timeWaiting,\n (maxWaitRef.current as number) - timeSinceLastInvoke\n )\n : timeWaiting;\n }, []);\n\n // Forward declare timerExpired for mutual recursion\n const timerExpiredRef = useRef<() => void>(() => {});\n\n // Helper function: trailingEdge\n const trailingEdge = useCallback(\n (time: number): ReturnType<T> | undefined => {\n timerIdRef.current = undefined;\n\n // Only invoke if trailing is true and we have args (meaning the function was called)\n if (trailingRef.current && lastArgsRef.current !== undefined) {\n return invokeFunc(time);\n }\n lastArgsRef.current = undefined;\n return resultRef.current;\n },\n [invokeFunc]\n );\n\n // Helper function: timerExpired\n const timerExpired = useCallback((): void => {\n const time = now();\n if (shouldInvoke(time)) {\n trailingEdge(time);\n } else {\n // Restart the timer\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n remainingWait(time)\n );\n }\n }, [now, shouldInvoke, trailingEdge, remainingWait]);\n\n // Update the ref after timerExpired is defined\n timerExpiredRef.current = timerExpired;\n\n // Helper function: leadingEdge\n const leadingEdge = useCallback(\n (time: number): ReturnType<T> | undefined => {\n // Reset any `maxWait` timer\n lastInvokeTimeRef.current = time;\n // Start the timer for the trailing edge\n timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);\n // Invoke the leading edge\n return leadingRef.current ? invokeFunc(time) : resultRef.current;\n },\n [invokeFunc]\n );\n\n // Cancel function\n const cancel = useCallback((): void => {\n if (timerIdRef.current !== undefined) {\n clearTimeout(timerIdRef.current);\n }\n lastInvokeTimeRef.current = 0;\n lastArgsRef.current = undefined;\n lastCallTimeRef.current = undefined;\n timerIdRef.current = undefined;\n }, []);\n\n // Flush function\n const flush = useCallback((): ReturnType<T> | undefined => {\n if (timerIdRef.current === undefined) {\n return resultRef.current;\n }\n return trailingEdge(now());\n }, [now, trailingEdge]);\n\n // Pending function\n const pending = useCallback((): boolean => {\n return timerIdRef.current !== undefined;\n }, []);\n\n // Main debounced function\n const debounced = useCallback(\n (...args: Parameters<T>): ReturnType<T> | undefined => {\n const time = now();\n const isInvoking = shouldInvoke(time);\n\n lastArgsRef.current = args;\n lastCallTimeRef.current = time;\n\n if (isInvoking) {\n if (timerIdRef.current === undefined) {\n return leadingEdge(time);\n }\n if (maxingRef.current) {\n // Handle invocations in a tight loop\n clearTimeout(timerIdRef.current);\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n waitRef.current\n );\n return invokeFunc(time);\n }\n }\n if (timerIdRef.current === undefined) {\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n waitRef.current\n );\n }\n return resultRef.current;\n },\n [now, shouldInvoke, leadingEdge, invokeFunc]\n );\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timerIdRef.current !== undefined) {\n clearTimeout(timerIdRef.current);\n }\n };\n }, []);\n\n // Create the debounced function with attached methods\n const debouncedWithMethods = useMemo(() => {\n const fn = debounced as DebouncedFunction<T>;\n fn.cancel = cancel;\n fn.flush = flush;\n fn.pending = pending;\n return fn;\n }, [debounced, cancel, flush, pending]);\n\n return debouncedWithMethods;\n}\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useDebounceCallback.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from \"react\";\r\n\r\n/**\r\n * Options for useDebounceCallback hook\r\n */\r\nexport interface UseDebounceCallbackOptions {\r\n /**\r\n * Maximum time the debounced function can be delayed\r\n * @default undefined (no maximum)\r\n */\r\n maxWait?: number;\r\n /**\r\n * Whether to invoke on the leading edge\r\n * @default false\r\n */\r\n leading?: boolean;\r\n /**\r\n * Whether to invoke on the trailing edge\r\n * @default true\r\n */\r\n trailing?: boolean;\r\n}\r\n\r\n/**\r\n * Debounced function interface with control methods\r\n */\r\nexport interface DebouncedFunction<T extends (...args: any[]) => any> {\r\n /**\r\n * Call the debounced function\r\n */\r\n (...args: Parameters<T>): ReturnType<T> | undefined;\r\n /**\r\n * Cancel any pending invocation\r\n */\r\n cancel: () => void;\r\n /**\r\n * Immediately invoke any pending invocation\r\n */\r\n flush: () => ReturnType<T> | undefined;\r\n /**\r\n * Check if there is a pending invocation\r\n */\r\n pending: () => boolean;\r\n}\r\n\r\n/**\r\n * Creates a debounced version of the provided callback function.\r\n * The debounced function delays invoking the callback until after `delay` milliseconds\r\n * have elapsed since the last time the debounced function was invoked.\r\n *\r\n * @template T - The type of the callback function\r\n * @param callback - The function to debounce\r\n * @param delay - The delay in milliseconds (default: 500ms)\r\n * @param options - Additional options for controlling debounce behavior\r\n * @returns A debounced version of the callback with cancel, flush, and pending methods\r\n *\r\n * @example\r\n * ```tsx\r\n * function SearchComponent() {\r\n * const [results, setResults] = useState([]);\r\n *\r\n * const debouncedSearch = useDebounceCallback(\r\n * async (query: string) => {\r\n * const data = await searchAPI(query);\r\n * setResults(data);\r\n * },\r\n * 500\r\n * );\r\n *\r\n * return (\r\n * <input\r\n * type=\"text\"\r\n * onChange={(e) => debouncedSearch(e.target.value)}\r\n * placeholder=\"Search...\"\r\n * />\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With leading edge invocation\r\n * const debouncedFn = useDebounceCallback(callback, 300, { leading: true });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With maximum wait time\r\n * const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });\r\n *\r\n * // Cancel pending invocation\r\n * debouncedFn.cancel();\r\n *\r\n * // Immediately invoke pending invocation\r\n * debouncedFn.flush();\r\n *\r\n * // Check if there's a pending invocation\r\n * if (debouncedFn.pending()) {\r\n * console.log('There is a pending call');\r\n * }\r\n * ```\r\n */\r\nexport function useDebounceCallback<T extends (...args: any[]) => any>(\r\n callback: T,\r\n delay: number = 500,\r\n options: UseDebounceCallbackOptions = {}\r\n): DebouncedFunction<T> {\r\n // Parse options\r\n const wait = delay || 0;\r\n const leading = options.leading ?? false;\r\n const trailing = options.trailing !== undefined ? options.trailing : true;\r\n const maxing = \"maxWait\" in options;\r\n const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : undefined;\r\n\r\n // Refs for mutable state\r\n const callbackRef = useRef<T>(callback);\r\n const timerIdRef = useRef<ReturnType<typeof setTimeout> | undefined>(\r\n undefined\r\n );\r\n const lastCallTimeRef = useRef<number | undefined>(undefined);\r\n const lastInvokeTimeRef = useRef<number>(0);\r\n const lastArgsRef = useRef<Parameters<T> | undefined>(undefined);\r\n const resultRef = useRef<ReturnType<T> | undefined>(undefined);\r\n\r\n // Store options in refs\r\n const waitRef = useRef(wait);\r\n const leadingRef = useRef(leading);\r\n const trailingRef = useRef(trailing);\r\n const maxingRef = useRef(maxing);\r\n const maxWaitRef = useRef(maxWait);\r\n\r\n // Update callback ref on every render to always have the latest callback\r\n callbackRef.current = callback;\r\n\r\n // Update option refs when options change\r\n waitRef.current = wait;\r\n leadingRef.current = leading;\r\n trailingRef.current = trailing;\r\n maxingRef.current = maxing;\r\n maxWaitRef.current = maxWait;\r\n\r\n // Helper function to get current time\r\n const now = useCallback(() => Date.now(), []);\r\n\r\n // Helper function: shouldInvoke\r\n const shouldInvoke = useCallback((time: number): boolean => {\r\n const lastCallTime = lastCallTimeRef.current;\r\n if (lastCallTime === undefined) {\r\n return true; // First call\r\n }\r\n\r\n const timeSinceLastCall = time - lastCallTime;\r\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\r\n\r\n return (\r\n timeSinceLastCall >= waitRef.current ||\r\n timeSinceLastCall < 0 || // System time went backwards\r\n (maxingRef.current &&\r\n timeSinceLastInvoke >= (maxWaitRef.current as number))\r\n );\r\n }, []);\r\n\r\n // Helper function: invokeFunc\r\n const invokeFunc = useCallback((time: number): ReturnType<T> | undefined => {\r\n const args = lastArgsRef.current;\r\n lastArgsRef.current = undefined;\r\n lastInvokeTimeRef.current = time;\r\n\r\n if (args !== undefined) {\r\n resultRef.current = callbackRef.current(...args);\r\n }\r\n return resultRef.current;\r\n }, []);\r\n\r\n // Helper function: remainingWait\r\n const remainingWait = useCallback((time: number): number => {\r\n const lastCallTime = lastCallTimeRef.current;\r\n if (lastCallTime === undefined) {\r\n return waitRef.current;\r\n }\r\n\r\n const timeSinceLastCall = time - lastCallTime;\r\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\r\n const timeWaiting = waitRef.current - timeSinceLastCall;\r\n\r\n return maxingRef.current\r\n ? Math.min(\r\n timeWaiting,\r\n (maxWaitRef.current as number) - timeSinceLastInvoke\r\n )\r\n : timeWaiting;\r\n }, []);\r\n\r\n // Forward declare timerExpired for mutual recursion\r\n const timerExpiredRef = useRef<() => void>(() => {});\r\n\r\n // Helper function: trailingEdge\r\n const trailingEdge = useCallback(\r\n (time: number): ReturnType<T> | undefined => {\r\n timerIdRef.current = undefined;\r\n\r\n // Only invoke if trailing is true and we have args (meaning the function was called)\r\n if (trailingRef.current && lastArgsRef.current !== undefined) {\r\n return invokeFunc(time);\r\n }\r\n lastArgsRef.current = undefined;\r\n return resultRef.current;\r\n },\r\n [invokeFunc]\r\n );\r\n\r\n // Helper function: timerExpired\r\n const timerExpired = useCallback((): void => {\r\n const time = now();\r\n if (shouldInvoke(time)) {\r\n trailingEdge(time);\r\n } else {\r\n // Restart the timer\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n remainingWait(time)\r\n );\r\n }\r\n }, [now, shouldInvoke, trailingEdge, remainingWait]);\r\n\r\n // Update the ref after timerExpired is defined\r\n timerExpiredRef.current = timerExpired;\r\n\r\n // Helper function: leadingEdge\r\n const leadingEdge = useCallback(\r\n (time: number): ReturnType<T> | undefined => {\r\n // Reset any `maxWait` timer\r\n lastInvokeTimeRef.current = time;\r\n // Start the timer for the trailing edge\r\n timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);\r\n // Invoke the leading edge\r\n return leadingRef.current ? invokeFunc(time) : resultRef.current;\r\n },\r\n [invokeFunc]\r\n );\r\n\r\n // Cancel function\r\n const cancel = useCallback((): void => {\r\n if (timerIdRef.current !== undefined) {\r\n clearTimeout(timerIdRef.current);\r\n }\r\n lastInvokeTimeRef.current = 0;\r\n lastArgsRef.current = undefined;\r\n lastCallTimeRef.current = undefined;\r\n timerIdRef.current = undefined;\r\n }, []);\r\n\r\n // Flush function\r\n const flush = useCallback((): ReturnType<T> | undefined => {\r\n if (timerIdRef.current === undefined) {\r\n return resultRef.current;\r\n }\r\n return trailingEdge(now());\r\n }, [now, trailingEdge]);\r\n\r\n // Pending function\r\n const pending = useCallback((): boolean => {\r\n return timerIdRef.current !== undefined;\r\n }, []);\r\n\r\n // Main debounced function\r\n const debounced = useCallback(\r\n (...args: Parameters<T>): ReturnType<T> | undefined => {\r\n const time = now();\r\n const isInvoking = shouldInvoke(time);\r\n\r\n lastArgsRef.current = args;\r\n lastCallTimeRef.current = time;\r\n\r\n if (isInvoking) {\r\n if (timerIdRef.current === undefined) {\r\n return leadingEdge(time);\r\n }\r\n if (maxingRef.current) {\r\n // Handle invocations in a tight loop\r\n clearTimeout(timerIdRef.current);\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n waitRef.current\r\n );\r\n return invokeFunc(time);\r\n }\r\n }\r\n if (timerIdRef.current === undefined) {\r\n timerIdRef.current = setTimeout(\r\n timerExpiredRef.current,\r\n waitRef.current\r\n );\r\n }\r\n return resultRef.current;\r\n },\r\n [now, shouldInvoke, leadingEdge, invokeFunc]\r\n );\r\n\r\n // Cleanup on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (timerIdRef.current !== undefined) {\r\n clearTimeout(timerIdRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n // Create the debounced function with attached methods\r\n const debouncedWithMethods = useMemo(() => {\r\n const fn = debounced as DebouncedFunction<T>;\r\n fn.cancel = cancel;\r\n fn.flush = flush;\r\n fn.pending = pending;\r\n return fn;\r\n }, [debounced, cancel, flush, pending]);\r\n\r\n return debouncedWithMethods;\r\n}\r\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,SAAS,cAAc;AAsGjD,SAAS,oBACd,UACA,QAAgB,KAChB,UAAsC,CAAC,GACjB;AAEtB,QAAM,OAAO,SAAS;AACtB,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,aAAa,SAAY,QAAQ,WAAW;AACrE,QAAM,SAAS,aAAa;AAC5B,QAAM,UAAU,SAAS,KAAK,IAAI,QAAQ,WAAW,GAAG,IAAI,IAAI;AAGhE,QAAM,cAAc,OAAU,QAAQ;AACtC,QAAM,aAAa;AAAA,IACjB;AAAA,EACF;AACA,QAAM,kBAAkB,OAA2B,MAAS;AAC5D,QAAM,oBAAoB,OAAe,CAAC;AAC1C,QAAM,cAAc,OAAkC,MAAS;AAC/D,QAAM,YAAY,OAAkC,MAAS;AAG7D,QAAM,UAAU,OAAO,IAAI;AAC3B,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,cAAc,OAAO,QAAQ;AACnC,QAAM,YAAY,OAAO,MAAM;AAC/B,QAAM,aAAa,OAAO,OAAO;AAGjC,cAAY,UAAU;AAGtB,UAAQ,UAAU;AAClB,aAAW,UAAU;AACrB,cAAY,UAAU;AACtB,YAAU,UAAU;AACpB,aAAW,UAAU;AAGrB,QAAM,MAAM,YAAY,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC;AAG5C,QAAM,eAAe,YAAY,CAAC,SAA0B;AAC1D,UAAM,eAAe,gBAAgB;AACrC,QAAI,iBAAiB,QAAW;AAC9B,aAAO;AAAA,IACT;AAEA,UAAM,oBAAoB,OAAO;AACjC,UAAM,sBAAsB,OAAO,kBAAkB;AAErD,WACE,qBAAqB,QAAQ,WAC7B,oBAAoB;AAAA,IACnB,UAAU,WACT,uBAAwB,WAAW;AAAA,EAEzC,GAAG,CAAC,CAAC;AAGL,QAAM,aAAa,YAAY,CAAC,SAA4C;AAC1E,UAAM,OAAO,YAAY;AACzB,gBAAY,UAAU;AACtB,sBAAkB,UAAU;AAE5B,QAAI,SAAS,QAAW;AACtB,gBAAU,UAAU,YAAY,QAAQ,GAAG,IAAI;AAAA,IACjD;AACA,WAAO,UAAU;AAAA,EACnB,GAAG,CAAC,CAAC;AAGL,QAAM,gBAAgB,YAAY,CAAC,SAAyB;AAC1D,UAAM,eAAe,gBAAgB;AACrC,QAAI,iBAAiB,QAAW;AAC9B,aAAO,QAAQ;AAAA,IACjB;AAEA,UAAM,oBAAoB,OAAO;AACjC,UAAM,sBAAsB,OAAO,kBAAkB;AACrD,UAAM,cAAc,QAAQ,UAAU;AAEtC,WAAO,UAAU,UACb,KAAK;AAAA,MACH;AAAA,MACC,WAAW,UAAqB;AAAA,IACnC,IACA;AAAA,EACN,GAAG,CAAC,CAAC;AAGL,QAAM,kBAAkB,OAAmB,MAAM;AAAA,EAAC,CAAC;AAGnD,QAAM,eAAe;AAAA,IACnB,CAAC,SAA4C;AAC3C,iBAAW,UAAU;AAGrB,UAAI,YAAY,WAAW,YAAY,YAAY,QAAW;AAC5D,eAAO,WAAW,IAAI;AAAA,MACxB;AACA,kBAAY,UAAU;AACtB,aAAO,UAAU;AAAA,IACnB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAGA,QAAM,eAAe,YAAY,MAAY;AAC3C,UAAM,OAAO,IAAI;AACjB,QAAI,aAAa,IAAI,GAAG;AACtB,mBAAa,IAAI;AAAA,IACnB,OAAO;AAEL,iBAAW,UAAU;AAAA,QACnB,gBAAgB;AAAA,QAChB,cAAc,IAAI;AAAA,MACpB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,KAAK,cAAc,cAAc,aAAa,CAAC;AAGnD,kBAAgB,UAAU;AAG1B,QAAM,cAAc;AAAA,IAClB,CAAC,SAA4C;AAE3C,wBAAkB,UAAU;AAE5B,iBAAW,UAAU,WAAW,gBAAgB,SAAS,QAAQ,OAAO;AAExE,aAAO,WAAW,UAAU,WAAW,IAAI,IAAI,UAAU;AAAA,IAC3D;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAGA,QAAM,SAAS,YAAY,MAAY;AACrC,QAAI,WAAW,YAAY,QAAW;AACpC,mBAAa,WAAW,OAAO;AAAA,IACjC;AACA,sBAAkB,UAAU;AAC5B,gBAAY,UAAU;AACtB,oBAAgB,UAAU;AAC1B,eAAW,UAAU;AAAA,EACvB,GAAG,CAAC,CAAC;AAGL,QAAM,QAAQ,YAAY,MAAiC;AACzD,QAAI,WAAW,YAAY,QAAW;AACpC,aAAO,UAAU;AAAA,IACnB;AACA,WAAO,aAAa,IAAI,CAAC;AAAA,EAC3B,GAAG,CAAC,KAAK,YAAY,CAAC;AAGtB,QAAM,UAAU,YAAY,MAAe;AACzC,WAAO,WAAW,YAAY;AAAA,EAChC,GAAG,CAAC,CAAC;AAGL,QAAM,YAAY;AAAA,IAChB,IAAI,SAAmD;AACrD,YAAM,OAAO,IAAI;AACjB,YAAM,aAAa,aAAa,IAAI;AAEpC,kBAAY,UAAU;AACtB,sBAAgB,UAAU;AAE1B,UAAI,YAAY;AACd,YAAI,WAAW,YAAY,QAAW;AACpC,iBAAO,YAAY,IAAI;AAAA,QACzB;AACA,YAAI,UAAU,SAAS;AAErB,uBAAa,WAAW,OAAO;AAC/B,qBAAW,UAAU;AAAA,YACnB,gBAAgB;AAAA,YAChB,QAAQ;AAAA,UACV;AACA,iBAAO,WAAW,IAAI;AAAA,QACxB;AAAA,MACF;AACA,UAAI,WAAW,YAAY,QAAW;AACpC,mBAAW,UAAU;AAAA,UACnB,gBAAgB;AAAA,UAChB,QAAQ;AAAA,QACV;AAAA,MACF;AACA,aAAO,UAAU;AAAA,IACnB;AAAA,IACA,CAAC,KAAK,cAAc,aAAa,UAAU;AAAA,EAC7C;AAGA,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,YAAY,QAAW;AACpC,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,uBAAuB,QAAQ,MAAM;AACzC,UAAM,KAAK;AACX,OAAG,SAAS;AACZ,OAAG,QAAQ;AACX,OAAG,UAAU;AACb,WAAO;AAAA,EACT,GAAG,CAAC,WAAW,QAAQ,OAAO,OAAO,CAAC;AAEtC,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/useDebounceCallback.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from \"react\";\n\n/**\n * Options for useDebounceCallback hook\n */\nexport interface UseDebounceCallbackOptions {\n /**\n * Maximum time the debounced function can be delayed\n * @default undefined (no maximum)\n */\n maxWait?: number;\n /**\n * Whether to invoke on the leading edge\n * @default false\n */\n leading?: boolean;\n /**\n * Whether to invoke on the trailing edge\n * @default true\n */\n trailing?: boolean;\n}\n\n/**\n * Debounced function interface with control methods\n */\nexport interface DebouncedFunction<T extends (...args: any[]) => any> {\n /**\n * Call the debounced function\n */\n (...args: Parameters<T>): ReturnType<T> | undefined;\n /**\n * Cancel any pending invocation\n */\n cancel: () => void;\n /**\n * Immediately invoke any pending invocation\n */\n flush: () => ReturnType<T> | undefined;\n /**\n * Check if there is a pending invocation\n */\n pending: () => boolean;\n}\n\n/**\n * Creates a debounced version of the provided callback function.\n * The debounced function delays invoking the callback until after `delay` milliseconds\n * have elapsed since the last time the debounced function was invoked.\n *\n * @template T - The type of the callback function\n * @param callback - The function to debounce\n * @param delay - The delay in milliseconds (default: 500ms)\n * @param options - Additional options for controlling debounce behavior\n * @returns A debounced version of the callback with cancel, flush, and pending methods\n *\n * @example\n * ```tsx\n * function SearchComponent() {\n * const [results, setResults] = useState([]);\n *\n * const debouncedSearch = useDebounceCallback(\n * async (query: string) => {\n * const data = await searchAPI(query);\n * setResults(data);\n * },\n * 500\n * );\n *\n * return (\n * <input\n * type=\"text\"\n * onChange={(e) => debouncedSearch(e.target.value)}\n * placeholder=\"Search...\"\n * />\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With leading edge invocation\n * const debouncedFn = useDebounceCallback(callback, 300, { leading: true });\n * ```\n *\n * @example\n * ```tsx\n * // With maximum wait time\n * const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });\n *\n * // Cancel pending invocation\n * debouncedFn.cancel();\n *\n * // Immediately invoke pending invocation\n * debouncedFn.flush();\n *\n * // Check if there's a pending invocation\n * if (debouncedFn.pending()) {\n * console.log('There is a pending call');\n * }\n * ```\n */\nexport function useDebounceCallback<T extends (...args: any[]) => any>(\n callback: T,\n delay: number = 500,\n options: UseDebounceCallbackOptions = {}\n): DebouncedFunction<T> {\n // Parse options\n const wait = delay || 0;\n const leading = options.leading ?? false;\n const trailing = options.trailing !== undefined ? options.trailing : true;\n const maxing = \"maxWait\" in options;\n const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : undefined;\n\n // Refs for mutable state\n const callbackRef = useRef<T>(callback);\n const timerIdRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined\n );\n const lastCallTimeRef = useRef<number | undefined>(undefined);\n const lastInvokeTimeRef = useRef<number>(0);\n const lastArgsRef = useRef<Parameters<T> | undefined>(undefined);\n const resultRef = useRef<ReturnType<T> | undefined>(undefined);\n\n // Store options in refs\n const waitRef = useRef(wait);\n const leadingRef = useRef(leading);\n const trailingRef = useRef(trailing);\n const maxingRef = useRef(maxing);\n const maxWaitRef = useRef(maxWait);\n\n // Update callback ref on every render to always have the latest callback\n callbackRef.current = callback;\n\n // Update option refs when options change\n waitRef.current = wait;\n leadingRef.current = leading;\n trailingRef.current = trailing;\n maxingRef.current = maxing;\n maxWaitRef.current = maxWait;\n\n // Helper function to get current time\n const now = useCallback(() => Date.now(), []);\n\n // Helper function: shouldInvoke\n const shouldInvoke = useCallback((time: number): boolean => {\n const lastCallTime = lastCallTimeRef.current;\n if (lastCallTime === undefined) {\n return true; // First call\n }\n\n const timeSinceLastCall = time - lastCallTime;\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\n\n return (\n timeSinceLastCall >= waitRef.current ||\n timeSinceLastCall < 0 || // System time went backwards\n (maxingRef.current &&\n timeSinceLastInvoke >= (maxWaitRef.current as number))\n );\n }, []);\n\n // Helper function: invokeFunc\n const invokeFunc = useCallback((time: number): ReturnType<T> | undefined => {\n const args = lastArgsRef.current;\n lastArgsRef.current = undefined;\n lastInvokeTimeRef.current = time;\n\n if (args !== undefined) {\n resultRef.current = callbackRef.current(...args);\n }\n return resultRef.current;\n }, []);\n\n // Helper function: remainingWait\n const remainingWait = useCallback((time: number): number => {\n const lastCallTime = lastCallTimeRef.current;\n if (lastCallTime === undefined) {\n return waitRef.current;\n }\n\n const timeSinceLastCall = time - lastCallTime;\n const timeSinceLastInvoke = time - lastInvokeTimeRef.current;\n const timeWaiting = waitRef.current - timeSinceLastCall;\n\n return maxingRef.current\n ? Math.min(\n timeWaiting,\n (maxWaitRef.current as number) - timeSinceLastInvoke\n )\n : timeWaiting;\n }, []);\n\n // Forward declare timerExpired for mutual recursion\n const timerExpiredRef = useRef<() => void>(() => {});\n\n // Helper function: trailingEdge\n const trailingEdge = useCallback(\n (time: number): ReturnType<T> | undefined => {\n timerIdRef.current = undefined;\n\n // Only invoke if trailing is true and we have args (meaning the function was called)\n if (trailingRef.current && lastArgsRef.current !== undefined) {\n return invokeFunc(time);\n }\n lastArgsRef.current = undefined;\n return resultRef.current;\n },\n [invokeFunc]\n );\n\n // Helper function: timerExpired\n const timerExpired = useCallback((): void => {\n const time = now();\n if (shouldInvoke(time)) {\n trailingEdge(time);\n } else {\n // Restart the timer\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n remainingWait(time)\n );\n }\n }, [now, shouldInvoke, trailingEdge, remainingWait]);\n\n // Update the ref after timerExpired is defined\n timerExpiredRef.current = timerExpired;\n\n // Helper function: leadingEdge\n const leadingEdge = useCallback(\n (time: number): ReturnType<T> | undefined => {\n // Reset any `maxWait` timer\n lastInvokeTimeRef.current = time;\n // Start the timer for the trailing edge\n timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);\n // Invoke the leading edge\n return leadingRef.current ? invokeFunc(time) : resultRef.current;\n },\n [invokeFunc]\n );\n\n // Cancel function\n const cancel = useCallback((): void => {\n if (timerIdRef.current !== undefined) {\n clearTimeout(timerIdRef.current);\n }\n lastInvokeTimeRef.current = 0;\n lastArgsRef.current = undefined;\n lastCallTimeRef.current = undefined;\n timerIdRef.current = undefined;\n }, []);\n\n // Flush function\n const flush = useCallback((): ReturnType<T> | undefined => {\n if (timerIdRef.current === undefined) {\n return resultRef.current;\n }\n return trailingEdge(now());\n }, [now, trailingEdge]);\n\n // Pending function\n const pending = useCallback((): boolean => {\n return timerIdRef.current !== undefined;\n }, []);\n\n // Main debounced function\n const debounced = useCallback(\n (...args: Parameters<T>): ReturnType<T> | undefined => {\n const time = now();\n const isInvoking = shouldInvoke(time);\n\n lastArgsRef.current = args;\n lastCallTimeRef.current = time;\n\n if (isInvoking) {\n if (timerIdRef.current === undefined) {\n return leadingEdge(time);\n }\n if (maxingRef.current) {\n // Handle invocations in a tight loop\n clearTimeout(timerIdRef.current);\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n waitRef.current\n );\n return invokeFunc(time);\n }\n }\n if (timerIdRef.current === undefined) {\n timerIdRef.current = setTimeout(\n timerExpiredRef.current,\n waitRef.current\n );\n }\n return resultRef.current;\n },\n [now, shouldInvoke, leadingEdge, invokeFunc]\n );\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (timerIdRef.current !== undefined) {\n clearTimeout(timerIdRef.current);\n }\n };\n }, []);\n\n // Create the debounced function with attached methods\n const debouncedWithMethods = useMemo(() => {\n const fn = debounced as DebouncedFunction<T>;\n fn.cancel = cancel;\n fn.flush = flush;\n fn.pending = pending;\n return fn;\n }, [debounced, cancel, flush, pending]);\n\n return debouncedWithMethods;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-debounce-callback",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "A React hook for debouncing callback functions",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",