useDebounce.js
5.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { useEffect, useMemo, useRef } from 'react';
function useDebouncedCallback(func, wait, options) {
var _this = this;
var lastCallTime = useRef(null);
var lastInvokeTime = useRef(0);
var timerId = useRef(null);
var lastArgs = useRef([]);
var lastThis = useRef();
var result = useRef();
var funcRef = useRef(func);
var mounted = useRef(true);
funcRef.current = func; // Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
var useRAF = !wait && wait !== 0 && typeof window !== 'undefined';
if (typeof func !== 'function') {
throw new TypeError('Expected a function');
}
wait = +wait || 0;
options = options || {};
var leading = !!options.leading;
var trailing = 'trailing' in options ? !!options.trailing : true; // `true` by default
var maxing = ('maxWait' in options);
var maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : null;
useEffect(function () {
mounted.current = true;
return function () {
mounted.current = false;
};
}, []); // You may have a question, why we have so many code under the useMemo definition.
//
// This was made as we want to escape from useCallback hell and
// not to initialize a number of functions each time useDebouncedCallback is called.
//
// It means that we have less garbage for our GC calls which improves performance.
// Also, it makes this library smaller.
//
// And the last reason, that the code without lots of useCallback with deps is easier to read.
// You have only one place for that.
var debounced = useMemo(function () {
var invokeFunc = function invokeFunc(time) {
var args = lastArgs.current;
var thisArg = lastThis.current;
lastArgs.current = lastThis.current = null;
lastInvokeTime.current = time;
return result.current = funcRef.current.apply(thisArg, args);
};
var startTimer = function startTimer(pendingFunc, wait) {
if (useRAF) cancelAnimationFrame(timerId.current);
timerId.current = useRAF ? requestAnimationFrame(pendingFunc) : setTimeout(pendingFunc, wait);
};
var shouldInvoke = function shouldInvoke(time) {
if (!mounted.current) return false;
var timeSinceLastCall = time - lastCallTime.current;
var timeSinceLastInvoke = time - lastInvokeTime.current; // Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return !lastCallTime.current || timeSinceLastCall >= wait || timeSinceLastCall < 0 || maxing && timeSinceLastInvoke >= maxWait;
};
var trailingEdge = function trailingEdge(time) {
timerId.current = null; // Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs.current) {
return invokeFunc(time);
}
lastArgs.current = lastThis.current = null;
return result.current;
};
var timerExpired = function timerExpired() {
var time = Date.now();
if (shouldInvoke(time)) {
return trailingEdge(time);
} // https://github.com/xnimorz/use-debounce/issues/97
if (!mounted.current) {
return;
} // Remaining wait calculation
var timeSinceLastCall = time - lastCallTime.current;
var timeSinceLastInvoke = time - lastInvokeTime.current;
var timeWaiting = wait - timeSinceLastCall;
var remainingWait = maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting; // Restart the timer
startTimer(timerExpired, remainingWait);
};
var func = function func() {
var time = Date.now();
var isInvoking = shouldInvoke(time);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
lastArgs.current = args;
lastThis.current = _this;
lastCallTime.current = time;
if (isInvoking) {
if (!timerId.current && mounted.current) {
// Reset any `maxWait` timer.
lastInvokeTime.current = lastCallTime.current; // Start the timer for the trailing edge.
startTimer(timerExpired, wait); // Invoke the leading edge.
return leading ? invokeFunc(lastCallTime.current) : result.current;
}
if (maxing) {
// Handle invocations in a tight loop.
startTimer(timerExpired, wait);
return invokeFunc(lastCallTime.current);
}
}
if (!timerId.current) {
startTimer(timerExpired, wait);
}
return result.current;
};
func.cancel = function () {
if (timerId.current) {
useRAF ? cancelAnimationFrame(timerId.current) : clearTimeout(timerId.current);
}
lastInvokeTime.current = 0;
lastArgs.current = lastCallTime.current = lastThis.current = timerId.current = null;
};
func.isPending = function () {
return !!timerId.current;
};
func.flush = function () {
return !timerId.current ? result.current : trailingEdge(Date.now());
};
return func;
}, [leading, maxing, wait, maxWait, trailing, useRAF]);
return debounced;
}
export default useDebouncedCallback;