Debouncing is a method or a way to execute a function when it is made sure that no further repeated event will be triggered in a given frame of time.
We have already seen how to implement normal debounce and debounce with an immediate flag.
Let us see how to create a useDebounce() hook in React with the immediate flag as it will behave normally as well depending upon the flag.
We will be using useRef() to track the timerId of setTimeout so that we can reset it if a subsequent full call is made within the defined time.
Also, we will wrap the logic inside the useCallback() to avoid needless re-renderings as the callback function returns a memoized function that only change when one of the dependency changes.
const useDebounce = (fn, delay, immediate = false) => {
// ref the timer
const timerId = useRef();
// create a memoized debounce
const debounce = useCallback(
function () {
// reference the context and args for the setTimeout function
let context = this,
args = arguments;
// should the function be called now? If immediate is true
// and not already in a timeout then the answer is: Yes
const callNow = immediate && !timerId.current;
// base case
// clear the timeout to assign the new timeout to it.
// when event is fired repeatedly then this helps to reset
clearTimeout(timerId.current);
// set the new timeout
timerId.current = setTimeout(function () {
// Inside the timeout function, clear the timeout variable
// which will let the next execution run when in 'immediate' mode
timerId.current = null;
// check if the function already ran with the immediate flag
if (!immediate) {
// call the original function with apply
fn.apply(context, args);
}
}, delay);
// immediate mode and no wait timer? Execute the function immediately
if (callNow) fn.apply(context, args);
},
[fn, delay, immediate]
);
return debounce;
};
Test Case 1: Without an immediate flag
Input:
const Example = () => {
const print = () => {
console.log("hello");
};
const debounced = useDebounce(print, 500);
useEffect(() => {
window.addEventListener("mousemove", debounced, false);
return () => {
window.removeEventListener("mousemove", debounced, false);
};
});
return <></>;
};
Output:
"hello" //after 500 millisecond delay when user stops moving mouse
Test Case 2: With immediate flag
Input:
const Example = () => {
const print = () => {
console.log("hello");
};
// immediate
const debounced = useDebounce(print, 500, true);
useEffect(() => {
window.addEventListener("mousemove", debounced, false);
return () => {
window.removeEventListener("mousemove", debounced, false);
};
});
return <></>;
};
Output:
"hello" //immediately only once till the mouse moving is not stopped
"hello" //immediately again once till the mouse moving is not stopped