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