import React, { useEffect, useRef } from 'react'; /** * Use requestAnimationFrame + setInterval with Hooks in a declarative way. * * @see https://stackoverflow.com/a/59274004/3723993 * @see https://overreacted.io/making-setinterval-declarative-with-react-hooks/ */ export function useThrottledRAF( callback: React.EffectCallback, delay: number | null, ): [React.MutableRefObject, React.MutableRefObject] { const intervalRef = useRef(null); const rafRef = useRef(null); const callbackRef = useRef(callback); // Remember the latest callback: // // Without this, if you change the callback, when setInterval ticks again, it // will still call your old callback. // // If you add `callback` to useEffect's deps, it will work fine but the // interval will be reset. useEffect(() => { callbackRef.current = callback; }, [callback]); // Set up the interval: useEffect(() => { if (typeof delay === 'number') { intervalRef.current = window.setInterval(() => { rafRef.current = window.requestAnimationFrame(() => { callbackRef.current(); }); }, delay); // Clear interval and RAF if the components is unmounted or the delay changes: return () => { window.clearInterval(intervalRef.current || 0); window.cancelAnimationFrame(rafRef.current || 0); }; } }, [delay]); // In case you want to manually clear the interval or RAF from the consuming component...: return [intervalRef, rafRef]; }