@usefy/use-debounce-callback 0.0.2
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/dist/index.d.mts +101 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +187 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +160 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for useDebounceCallback hook
|
|
3
|
+
*/
|
|
4
|
+
interface UseDebounceCallbackOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Maximum time the debounced function can be delayed
|
|
7
|
+
* @default undefined (no maximum)
|
|
8
|
+
*/
|
|
9
|
+
maxWait?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to invoke on the leading edge
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
leading?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to invoke on the trailing edge
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
trailing?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Debounced function interface with control methods
|
|
23
|
+
*/
|
|
24
|
+
interface DebouncedFunction<T extends (...args: any[]) => any> {
|
|
25
|
+
/**
|
|
26
|
+
* Call the debounced function
|
|
27
|
+
*/
|
|
28
|
+
(...args: Parameters<T>): ReturnType<T> | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Cancel any pending invocation
|
|
31
|
+
*/
|
|
32
|
+
cancel: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Immediately invoke any pending invocation
|
|
35
|
+
*/
|
|
36
|
+
flush: () => ReturnType<T> | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Check if there is a pending invocation
|
|
39
|
+
*/
|
|
40
|
+
pending: () => boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a debounced version of the provided callback function.
|
|
44
|
+
* The debounced function delays invoking the callback until after `delay` milliseconds
|
|
45
|
+
* have elapsed since the last time the debounced function was invoked.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The type of the callback function
|
|
48
|
+
* @param callback - The function to debounce
|
|
49
|
+
* @param delay - The delay in milliseconds (default: 500ms)
|
|
50
|
+
* @param options - Additional options for controlling debounce behavior
|
|
51
|
+
* @returns A debounced version of the callback with cancel, flush, and pending methods
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* function SearchComponent() {
|
|
56
|
+
* const [results, setResults] = useState([]);
|
|
57
|
+
*
|
|
58
|
+
* const debouncedSearch = useDebounceCallback(
|
|
59
|
+
* async (query: string) => {
|
|
60
|
+
* const data = await searchAPI(query);
|
|
61
|
+
* setResults(data);
|
|
62
|
+
* },
|
|
63
|
+
* 500
|
|
64
|
+
* );
|
|
65
|
+
*
|
|
66
|
+
* return (
|
|
67
|
+
* <input
|
|
68
|
+
* type="text"
|
|
69
|
+
* onChange={(e) => debouncedSearch(e.target.value)}
|
|
70
|
+
* placeholder="Search..."
|
|
71
|
+
* />
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // With leading edge invocation
|
|
79
|
+
* const debouncedFn = useDebounceCallback(callback, 300, { leading: true });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* // With maximum wait time
|
|
85
|
+
* const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });
|
|
86
|
+
*
|
|
87
|
+
* // Cancel pending invocation
|
|
88
|
+
* debouncedFn.cancel();
|
|
89
|
+
*
|
|
90
|
+
* // Immediately invoke pending invocation
|
|
91
|
+
* debouncedFn.flush();
|
|
92
|
+
*
|
|
93
|
+
* // Check if there's a pending invocation
|
|
94
|
+
* if (debouncedFn.pending()) {
|
|
95
|
+
* console.log('There is a pending call');
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare function useDebounceCallback<T extends (...args: any[]) => any>(callback: T, delay?: number, options?: UseDebounceCallbackOptions): DebouncedFunction<T>;
|
|
100
|
+
|
|
101
|
+
export { type DebouncedFunction, type UseDebounceCallbackOptions, useDebounceCallback };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for useDebounceCallback hook
|
|
3
|
+
*/
|
|
4
|
+
interface UseDebounceCallbackOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Maximum time the debounced function can be delayed
|
|
7
|
+
* @default undefined (no maximum)
|
|
8
|
+
*/
|
|
9
|
+
maxWait?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to invoke on the leading edge
|
|
12
|
+
* @default false
|
|
13
|
+
*/
|
|
14
|
+
leading?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to invoke on the trailing edge
|
|
17
|
+
* @default true
|
|
18
|
+
*/
|
|
19
|
+
trailing?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Debounced function interface with control methods
|
|
23
|
+
*/
|
|
24
|
+
interface DebouncedFunction<T extends (...args: any[]) => any> {
|
|
25
|
+
/**
|
|
26
|
+
* Call the debounced function
|
|
27
|
+
*/
|
|
28
|
+
(...args: Parameters<T>): ReturnType<T> | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Cancel any pending invocation
|
|
31
|
+
*/
|
|
32
|
+
cancel: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Immediately invoke any pending invocation
|
|
35
|
+
*/
|
|
36
|
+
flush: () => ReturnType<T> | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Check if there is a pending invocation
|
|
39
|
+
*/
|
|
40
|
+
pending: () => boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a debounced version of the provided callback function.
|
|
44
|
+
* The debounced function delays invoking the callback until after `delay` milliseconds
|
|
45
|
+
* have elapsed since the last time the debounced function was invoked.
|
|
46
|
+
*
|
|
47
|
+
* @template T - The type of the callback function
|
|
48
|
+
* @param callback - The function to debounce
|
|
49
|
+
* @param delay - The delay in milliseconds (default: 500ms)
|
|
50
|
+
* @param options - Additional options for controlling debounce behavior
|
|
51
|
+
* @returns A debounced version of the callback with cancel, flush, and pending methods
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* function SearchComponent() {
|
|
56
|
+
* const [results, setResults] = useState([]);
|
|
57
|
+
*
|
|
58
|
+
* const debouncedSearch = useDebounceCallback(
|
|
59
|
+
* async (query: string) => {
|
|
60
|
+
* const data = await searchAPI(query);
|
|
61
|
+
* setResults(data);
|
|
62
|
+
* },
|
|
63
|
+
* 500
|
|
64
|
+
* );
|
|
65
|
+
*
|
|
66
|
+
* return (
|
|
67
|
+
* <input
|
|
68
|
+
* type="text"
|
|
69
|
+
* onChange={(e) => debouncedSearch(e.target.value)}
|
|
70
|
+
* placeholder="Search..."
|
|
71
|
+
* />
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // With leading edge invocation
|
|
79
|
+
* const debouncedFn = useDebounceCallback(callback, 300, { leading: true });
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```tsx
|
|
84
|
+
* // With maximum wait time
|
|
85
|
+
* const debouncedFn = useDebounceCallback(callback, 500, { maxWait: 2000 });
|
|
86
|
+
*
|
|
87
|
+
* // Cancel pending invocation
|
|
88
|
+
* debouncedFn.cancel();
|
|
89
|
+
*
|
|
90
|
+
* // Immediately invoke pending invocation
|
|
91
|
+
* debouncedFn.flush();
|
|
92
|
+
*
|
|
93
|
+
* // Check if there's a pending invocation
|
|
94
|
+
* if (debouncedFn.pending()) {
|
|
95
|
+
* console.log('There is a pending call');
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
declare function useDebounceCallback<T extends (...args: any[]) => any>(callback: T, delay?: number, options?: UseDebounceCallbackOptions): DebouncedFunction<T>;
|
|
100
|
+
|
|
101
|
+
export { type DebouncedFunction, type UseDebounceCallbackOptions, useDebounceCallback };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
useDebounceCallback: () => useDebounceCallback
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/useDebounceCallback.ts
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
function useDebounceCallback(callback, delay = 500, options = {}) {
|
|
30
|
+
const wait = delay || 0;
|
|
31
|
+
const leading = options.leading ?? false;
|
|
32
|
+
const trailing = options.trailing !== void 0 ? options.trailing : true;
|
|
33
|
+
const maxing = "maxWait" in options;
|
|
34
|
+
const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : void 0;
|
|
35
|
+
const callbackRef = (0, import_react.useRef)(callback);
|
|
36
|
+
const timerIdRef = (0, import_react.useRef)(
|
|
37
|
+
void 0
|
|
38
|
+
);
|
|
39
|
+
const lastCallTimeRef = (0, import_react.useRef)(void 0);
|
|
40
|
+
const lastInvokeTimeRef = (0, import_react.useRef)(0);
|
|
41
|
+
const lastArgsRef = (0, import_react.useRef)(void 0);
|
|
42
|
+
const resultRef = (0, import_react.useRef)(void 0);
|
|
43
|
+
const waitRef = (0, import_react.useRef)(wait);
|
|
44
|
+
const leadingRef = (0, import_react.useRef)(leading);
|
|
45
|
+
const trailingRef = (0, import_react.useRef)(trailing);
|
|
46
|
+
const maxingRef = (0, import_react.useRef)(maxing);
|
|
47
|
+
const maxWaitRef = (0, import_react.useRef)(maxWait);
|
|
48
|
+
callbackRef.current = callback;
|
|
49
|
+
waitRef.current = wait;
|
|
50
|
+
leadingRef.current = leading;
|
|
51
|
+
trailingRef.current = trailing;
|
|
52
|
+
maxingRef.current = maxing;
|
|
53
|
+
maxWaitRef.current = maxWait;
|
|
54
|
+
const now = (0, import_react.useCallback)(() => Date.now(), []);
|
|
55
|
+
const shouldInvoke = (0, import_react.useCallback)((time) => {
|
|
56
|
+
const lastCallTime = lastCallTimeRef.current;
|
|
57
|
+
if (lastCallTime === void 0) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const timeSinceLastCall = time - lastCallTime;
|
|
61
|
+
const timeSinceLastInvoke = time - lastInvokeTimeRef.current;
|
|
62
|
+
return timeSinceLastCall >= waitRef.current || timeSinceLastCall < 0 || // System time went backwards
|
|
63
|
+
maxingRef.current && timeSinceLastInvoke >= maxWaitRef.current;
|
|
64
|
+
}, []);
|
|
65
|
+
const invokeFunc = (0, import_react.useCallback)((time) => {
|
|
66
|
+
const args = lastArgsRef.current;
|
|
67
|
+
lastArgsRef.current = void 0;
|
|
68
|
+
lastInvokeTimeRef.current = time;
|
|
69
|
+
if (args !== void 0) {
|
|
70
|
+
resultRef.current = callbackRef.current(...args);
|
|
71
|
+
}
|
|
72
|
+
return resultRef.current;
|
|
73
|
+
}, []);
|
|
74
|
+
const remainingWait = (0, import_react.useCallback)((time) => {
|
|
75
|
+
const lastCallTime = lastCallTimeRef.current;
|
|
76
|
+
if (lastCallTime === void 0) {
|
|
77
|
+
return waitRef.current;
|
|
78
|
+
}
|
|
79
|
+
const timeSinceLastCall = time - lastCallTime;
|
|
80
|
+
const timeSinceLastInvoke = time - lastInvokeTimeRef.current;
|
|
81
|
+
const timeWaiting = waitRef.current - timeSinceLastCall;
|
|
82
|
+
return maxingRef.current ? Math.min(
|
|
83
|
+
timeWaiting,
|
|
84
|
+
maxWaitRef.current - timeSinceLastInvoke
|
|
85
|
+
) : timeWaiting;
|
|
86
|
+
}, []);
|
|
87
|
+
const timerExpiredRef = (0, import_react.useRef)(() => {
|
|
88
|
+
});
|
|
89
|
+
const trailingEdge = (0, import_react.useCallback)(
|
|
90
|
+
(time) => {
|
|
91
|
+
timerIdRef.current = void 0;
|
|
92
|
+
if (trailingRef.current && lastArgsRef.current !== void 0) {
|
|
93
|
+
return invokeFunc(time);
|
|
94
|
+
}
|
|
95
|
+
lastArgsRef.current = void 0;
|
|
96
|
+
return resultRef.current;
|
|
97
|
+
},
|
|
98
|
+
[invokeFunc]
|
|
99
|
+
);
|
|
100
|
+
const timerExpired = (0, import_react.useCallback)(() => {
|
|
101
|
+
const time = now();
|
|
102
|
+
if (shouldInvoke(time)) {
|
|
103
|
+
trailingEdge(time);
|
|
104
|
+
} else {
|
|
105
|
+
timerIdRef.current = setTimeout(
|
|
106
|
+
timerExpiredRef.current,
|
|
107
|
+
remainingWait(time)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}, [now, shouldInvoke, trailingEdge, remainingWait]);
|
|
111
|
+
timerExpiredRef.current = timerExpired;
|
|
112
|
+
const leadingEdge = (0, import_react.useCallback)(
|
|
113
|
+
(time) => {
|
|
114
|
+
lastInvokeTimeRef.current = time;
|
|
115
|
+
timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);
|
|
116
|
+
return leadingRef.current ? invokeFunc(time) : resultRef.current;
|
|
117
|
+
},
|
|
118
|
+
[invokeFunc]
|
|
119
|
+
);
|
|
120
|
+
const cancel = (0, import_react.useCallback)(() => {
|
|
121
|
+
if (timerIdRef.current !== void 0) {
|
|
122
|
+
clearTimeout(timerIdRef.current);
|
|
123
|
+
}
|
|
124
|
+
lastInvokeTimeRef.current = 0;
|
|
125
|
+
lastArgsRef.current = void 0;
|
|
126
|
+
lastCallTimeRef.current = void 0;
|
|
127
|
+
timerIdRef.current = void 0;
|
|
128
|
+
}, []);
|
|
129
|
+
const flush = (0, import_react.useCallback)(() => {
|
|
130
|
+
if (timerIdRef.current === void 0) {
|
|
131
|
+
return resultRef.current;
|
|
132
|
+
}
|
|
133
|
+
return trailingEdge(now());
|
|
134
|
+
}, [now, trailingEdge]);
|
|
135
|
+
const pending = (0, import_react.useCallback)(() => {
|
|
136
|
+
return timerIdRef.current !== void 0;
|
|
137
|
+
}, []);
|
|
138
|
+
const debounced = (0, import_react.useCallback)(
|
|
139
|
+
(...args) => {
|
|
140
|
+
const time = now();
|
|
141
|
+
const isInvoking = shouldInvoke(time);
|
|
142
|
+
lastArgsRef.current = args;
|
|
143
|
+
lastCallTimeRef.current = time;
|
|
144
|
+
if (isInvoking) {
|
|
145
|
+
if (timerIdRef.current === void 0) {
|
|
146
|
+
return leadingEdge(time);
|
|
147
|
+
}
|
|
148
|
+
if (maxingRef.current) {
|
|
149
|
+
clearTimeout(timerIdRef.current);
|
|
150
|
+
timerIdRef.current = setTimeout(
|
|
151
|
+
timerExpiredRef.current,
|
|
152
|
+
waitRef.current
|
|
153
|
+
);
|
|
154
|
+
return invokeFunc(time);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (timerIdRef.current === void 0) {
|
|
158
|
+
timerIdRef.current = setTimeout(
|
|
159
|
+
timerExpiredRef.current,
|
|
160
|
+
waitRef.current
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return resultRef.current;
|
|
164
|
+
},
|
|
165
|
+
[now, shouldInvoke, leadingEdge, invokeFunc]
|
|
166
|
+
);
|
|
167
|
+
(0, import_react.useEffect)(() => {
|
|
168
|
+
return () => {
|
|
169
|
+
if (timerIdRef.current !== void 0) {
|
|
170
|
+
clearTimeout(timerIdRef.current);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}, []);
|
|
174
|
+
const debouncedWithMethods = (0, import_react.useMemo)(() => {
|
|
175
|
+
const fn = debounced;
|
|
176
|
+
fn.cancel = cancel;
|
|
177
|
+
fn.flush = flush;
|
|
178
|
+
fn.pending = pending;
|
|
179
|
+
return fn;
|
|
180
|
+
}, [debounced, cancel, flush, pending]);
|
|
181
|
+
return debouncedWithMethods;
|
|
182
|
+
}
|
|
183
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
184
|
+
0 && (module.exports = {
|
|
185
|
+
useDebounceCallback
|
|
186
|
+
});
|
|
187
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/useDebounceCallback.ts
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
function useDebounceCallback(callback, delay = 500, options = {}) {
|
|
4
|
+
const wait = delay || 0;
|
|
5
|
+
const leading = options.leading ?? false;
|
|
6
|
+
const trailing = options.trailing !== void 0 ? options.trailing : true;
|
|
7
|
+
const maxing = "maxWait" in options;
|
|
8
|
+
const maxWait = maxing ? Math.max(options.maxWait || 0, wait) : void 0;
|
|
9
|
+
const callbackRef = useRef(callback);
|
|
10
|
+
const timerIdRef = useRef(
|
|
11
|
+
void 0
|
|
12
|
+
);
|
|
13
|
+
const lastCallTimeRef = useRef(void 0);
|
|
14
|
+
const lastInvokeTimeRef = useRef(0);
|
|
15
|
+
const lastArgsRef = useRef(void 0);
|
|
16
|
+
const resultRef = useRef(void 0);
|
|
17
|
+
const waitRef = useRef(wait);
|
|
18
|
+
const leadingRef = useRef(leading);
|
|
19
|
+
const trailingRef = useRef(trailing);
|
|
20
|
+
const maxingRef = useRef(maxing);
|
|
21
|
+
const maxWaitRef = useRef(maxWait);
|
|
22
|
+
callbackRef.current = callback;
|
|
23
|
+
waitRef.current = wait;
|
|
24
|
+
leadingRef.current = leading;
|
|
25
|
+
trailingRef.current = trailing;
|
|
26
|
+
maxingRef.current = maxing;
|
|
27
|
+
maxWaitRef.current = maxWait;
|
|
28
|
+
const now = useCallback(() => Date.now(), []);
|
|
29
|
+
const shouldInvoke = useCallback((time) => {
|
|
30
|
+
const lastCallTime = lastCallTimeRef.current;
|
|
31
|
+
if (lastCallTime === void 0) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
const timeSinceLastCall = time - lastCallTime;
|
|
35
|
+
const timeSinceLastInvoke = time - lastInvokeTimeRef.current;
|
|
36
|
+
return timeSinceLastCall >= waitRef.current || timeSinceLastCall < 0 || // System time went backwards
|
|
37
|
+
maxingRef.current && timeSinceLastInvoke >= maxWaitRef.current;
|
|
38
|
+
}, []);
|
|
39
|
+
const invokeFunc = useCallback((time) => {
|
|
40
|
+
const args = lastArgsRef.current;
|
|
41
|
+
lastArgsRef.current = void 0;
|
|
42
|
+
lastInvokeTimeRef.current = time;
|
|
43
|
+
if (args !== void 0) {
|
|
44
|
+
resultRef.current = callbackRef.current(...args);
|
|
45
|
+
}
|
|
46
|
+
return resultRef.current;
|
|
47
|
+
}, []);
|
|
48
|
+
const remainingWait = useCallback((time) => {
|
|
49
|
+
const lastCallTime = lastCallTimeRef.current;
|
|
50
|
+
if (lastCallTime === void 0) {
|
|
51
|
+
return waitRef.current;
|
|
52
|
+
}
|
|
53
|
+
const timeSinceLastCall = time - lastCallTime;
|
|
54
|
+
const timeSinceLastInvoke = time - lastInvokeTimeRef.current;
|
|
55
|
+
const timeWaiting = waitRef.current - timeSinceLastCall;
|
|
56
|
+
return maxingRef.current ? Math.min(
|
|
57
|
+
timeWaiting,
|
|
58
|
+
maxWaitRef.current - timeSinceLastInvoke
|
|
59
|
+
) : timeWaiting;
|
|
60
|
+
}, []);
|
|
61
|
+
const timerExpiredRef = useRef(() => {
|
|
62
|
+
});
|
|
63
|
+
const trailingEdge = useCallback(
|
|
64
|
+
(time) => {
|
|
65
|
+
timerIdRef.current = void 0;
|
|
66
|
+
if (trailingRef.current && lastArgsRef.current !== void 0) {
|
|
67
|
+
return invokeFunc(time);
|
|
68
|
+
}
|
|
69
|
+
lastArgsRef.current = void 0;
|
|
70
|
+
return resultRef.current;
|
|
71
|
+
},
|
|
72
|
+
[invokeFunc]
|
|
73
|
+
);
|
|
74
|
+
const timerExpired = useCallback(() => {
|
|
75
|
+
const time = now();
|
|
76
|
+
if (shouldInvoke(time)) {
|
|
77
|
+
trailingEdge(time);
|
|
78
|
+
} else {
|
|
79
|
+
timerIdRef.current = setTimeout(
|
|
80
|
+
timerExpiredRef.current,
|
|
81
|
+
remainingWait(time)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}, [now, shouldInvoke, trailingEdge, remainingWait]);
|
|
85
|
+
timerExpiredRef.current = timerExpired;
|
|
86
|
+
const leadingEdge = useCallback(
|
|
87
|
+
(time) => {
|
|
88
|
+
lastInvokeTimeRef.current = time;
|
|
89
|
+
timerIdRef.current = setTimeout(timerExpiredRef.current, waitRef.current);
|
|
90
|
+
return leadingRef.current ? invokeFunc(time) : resultRef.current;
|
|
91
|
+
},
|
|
92
|
+
[invokeFunc]
|
|
93
|
+
);
|
|
94
|
+
const cancel = useCallback(() => {
|
|
95
|
+
if (timerIdRef.current !== void 0) {
|
|
96
|
+
clearTimeout(timerIdRef.current);
|
|
97
|
+
}
|
|
98
|
+
lastInvokeTimeRef.current = 0;
|
|
99
|
+
lastArgsRef.current = void 0;
|
|
100
|
+
lastCallTimeRef.current = void 0;
|
|
101
|
+
timerIdRef.current = void 0;
|
|
102
|
+
}, []);
|
|
103
|
+
const flush = useCallback(() => {
|
|
104
|
+
if (timerIdRef.current === void 0) {
|
|
105
|
+
return resultRef.current;
|
|
106
|
+
}
|
|
107
|
+
return trailingEdge(now());
|
|
108
|
+
}, [now, trailingEdge]);
|
|
109
|
+
const pending = useCallback(() => {
|
|
110
|
+
return timerIdRef.current !== void 0;
|
|
111
|
+
}, []);
|
|
112
|
+
const debounced = useCallback(
|
|
113
|
+
(...args) => {
|
|
114
|
+
const time = now();
|
|
115
|
+
const isInvoking = shouldInvoke(time);
|
|
116
|
+
lastArgsRef.current = args;
|
|
117
|
+
lastCallTimeRef.current = time;
|
|
118
|
+
if (isInvoking) {
|
|
119
|
+
if (timerIdRef.current === void 0) {
|
|
120
|
+
return leadingEdge(time);
|
|
121
|
+
}
|
|
122
|
+
if (maxingRef.current) {
|
|
123
|
+
clearTimeout(timerIdRef.current);
|
|
124
|
+
timerIdRef.current = setTimeout(
|
|
125
|
+
timerExpiredRef.current,
|
|
126
|
+
waitRef.current
|
|
127
|
+
);
|
|
128
|
+
return invokeFunc(time);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (timerIdRef.current === void 0) {
|
|
132
|
+
timerIdRef.current = setTimeout(
|
|
133
|
+
timerExpiredRef.current,
|
|
134
|
+
waitRef.current
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return resultRef.current;
|
|
138
|
+
},
|
|
139
|
+
[now, shouldInvoke, leadingEdge, invokeFunc]
|
|
140
|
+
);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
return () => {
|
|
143
|
+
if (timerIdRef.current !== void 0) {
|
|
144
|
+
clearTimeout(timerIdRef.current);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}, []);
|
|
148
|
+
const debouncedWithMethods = useMemo(() => {
|
|
149
|
+
const fn = debounced;
|
|
150
|
+
fn.cancel = cancel;
|
|
151
|
+
fn.flush = flush;
|
|
152
|
+
fn.pending = pending;
|
|
153
|
+
return fn;
|
|
154
|
+
}, [debounced, cancel, flush, pending]);
|
|
155
|
+
return debouncedWithMethods;
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
useDebounceCallback
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usefy/use-debounce-callback",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A React hook for debouncing callback functions",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
24
|
+
"@testing-library/react": "^16.3.1",
|
|
25
|
+
"@testing-library/user-event": "^14.6.1",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"jsdom": "^27.3.0",
|
|
28
|
+
"react": "^19.0.0",
|
|
29
|
+
"rimraf": "^6.0.1",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"typescript": "^5.0.0",
|
|
32
|
+
"vitest": "^4.0.16"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/geon0529/usefy.git",
|
|
40
|
+
"directory": "packages/use-debounce-callback"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"hooks",
|
|
46
|
+
"debounce",
|
|
47
|
+
"callback",
|
|
48
|
+
"throttle"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"test": "vitest run",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"clean": "rimraf dist"
|
|
57
|
+
}
|
|
58
|
+
}
|