JAVASCRIPT
Throttling Expensive Operations with `useThrottle` Hook
Discover how to create a `useThrottle` React hook to control the execution rate of functions, preventing performance issues from rapid event firing like scrolling or resizing.
import { useRef, useCallback, useEffect } from 'react';
function useThrottle(func, delay) {
const timeoutRef = useRef(null);
const lastArgs = useRef(null);
const lastThis = useRef(null);
const throttledFunc = useCallback((...args) => {
lastArgs.current = args;
lastThis.current = this; // Capture 'this' context
if (!timeoutRef.current) {
timeoutRef.current = setTimeout(() => {
func.apply(lastThis.current, lastArgs.current);
timeoutRef.current = null; // Reset timeout
lastArgs.current = null;
lastThis.current = null;
}, delay);
}
}, [func, delay]);
// Clear timeout on unmount
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []); // Empty dependency array means this runs once on mount and unmount
return throttledFunc;
}
// Example Usage:
/*
function ThrottledScrollLogger() {
const handleScroll = useCallback(() => {
console.log('Scroll event!', new Date().toLocaleTimeString());
}, []);
const throttledHandleScroll = useThrottle(handleScroll, 1000); // Throttle to 1 second
useEffect(() => {
window.addEventListener('scroll', throttledHandleScroll);
return () => window.removeEventListener('scroll', throttledHandleScroll);
}, [throttledHandleScroll]);
return (
<div style={{ height: '2000px', background: 'lightblue', padding: '20px' }}>
Scroll down to see throttled console logs.
</div>
);
}
*/
How it works: The `useThrottle` hook creates a throttled version of a given function `func`. It ensures that `func` is executed at most once within a specified `delay` period. It uses `useRef` to store the timeout ID and the arguments/context of the last call. The `useCallback` wrapper ensures the throttled function maintains referential stability. When `throttledFunc` is called, it sets a timer only if one isn't already active. If a timer is active, it just updates the `lastArgs` and `lastThis`, deferring the execution until the current timer expires. A `useEffect` cleanup ensures any pending timeout is cleared when the component unmounts.