"use client"; import * as React from "react"; /** * Executes a callback function at specified intervals with automatic cleanup. * * @remarks * This hook provides a declarative interface for `setInterval` that automatically * handles cleanup on unmount and ensures the latest callback is always invoked * (preventing stale closures). Setting the delay to `null` pauses the interval, * which is useful for implementing play/pause functionality. * * Unlike raw `setInterval`, this hook guarantees that the interval is cleared * when the component unmounts or when the delay changes, preventing memory leaks * and unexpected behavior. * * @param callback - The function to execute at each interval. * @param delay - The interval delay in milliseconds, or `null` to pause the interval. * * @example * ```tsx * function Timer() { * const [count, setCount] = useState(0); * * useInterval(() => { * setCount((c) => c + 1); * }, 1000); * * return
Count: {count}
; * } * ``` * * @example * ```tsx * function PausableTimer() { * const [count, setCount] = useState(0); * const [isRunning, setIsRunning] = useState(true); * * useInterval( * () => { * setCount((c) => c + 1); * }, * isRunning ? 1000 : null, * ); * * return ( *
*
Count: {count}
* *
* ); * } * ``` */ export function useInterval(callback: () => void, delay: number | null): void { const savedCallbackRef = React.useRef(callback); // Update ref to latest callback on every render to avoid stale closures React.useEffect(() => { savedCallbackRef.current = callback; }, [callback]); React.useEffect(() => { // Don't schedule if delay is null if (delay === null) { return; } const intervalId = globalThis.setInterval(() => { savedCallbackRef.current(); }, delay); return () => { globalThis.clearInterval(intervalId); }; }, [delay]); }