use-hook-kits
Version:
  
91 lines (77 loc) • 2.93 kB
JavaScript
import { useRef, useCallback, useEffect } from 'react';
//{ maxWait?: number; leading?: boolean; trailing?: boolean } = {}
export default function useThrottleCallback(
callback,
delay,
options= {}
) {
const maxWait = options.maxWait;
const maxWaitHandler = useRef(null);
const maxWaitArgs = useRef([]);
const leading = options?.leading === undefined ? true : options.leading;
const trailing = options?.trailing === undefined ? false : options.trailing;
const leadingCall = useRef(false);
const functionTimeoutHandler = useRef(null);
const isComponentUnmounted = useRef(false);
const throttleFunction = useRef(callback);
throttleFunction.current = callback;
const cancelthrottledCallback = useCallback(() => {
clearTimeout(functionTimeoutHandler.current);
clearTimeout(maxWaitHandler.current);
maxWaitHandler.current = null;
maxWaitArgs.current = [];
functionTimeoutHandler.current = null;
leadingCall.current = false;
}, []);
useEffect(() => {
// We have to set isComponentUnmounted to be truth, as fast-refresh runs all useEffects
isComponentUnmounted.current = false;
return () => {
// we use flag, as we allow to call callPending outside the hook
isComponentUnmounted.current = true;
};
}, []);
const throttledCallback = useCallback(
(...args) => {
maxWaitArgs.current = args;
clearTimeout(functionTimeoutHandler.current);
if (leadingCall.current) {
leadingCall.current = false;
}
if (!functionTimeoutHandler.current && leading && !leadingCall.current) {
throttleFunction.current(...args);
leadingCall.current = true;
}
functionTimeoutHandler.current = setTimeout(() => {
let shouldCallFunction = true;
if (leading && leadingCall.current) {
shouldCallFunction = false;
}
cancelthrottledCallback();
if (!isComponentUnmounted.current && trailing && shouldCallFunction) {
throttleFunction.current(...args);
}
}, delay);
if (maxWait && !maxWaitHandler.current && trailing) {
maxWaitHandler.current = setTimeout(() => {
const args = maxWaitArgs.current;
cancelthrottledCallback();
if (!isComponentUnmounted.current) {
throttleFunction.current.apply(null, args);
}
}, maxWait);
}
},
[maxWait, delay, cancelthrottledCallback, leading, trailing]
);
const callPending = useCallback(() => {
// Call pending callback only if we have anything in our queue
if (!functionTimeoutHandler.current) {
return;
}
throttleFunction.current.apply(null, maxWaitArgs.current);
cancelthrottledCallback();
}, [cancelthrottledCallback]);
// At the moment, we use 3 args array so that we save backward compatibility
return [throttledCallback, cancelthrottledCallback, callPending];
}