useInterval
EditRepeatedly invokes the latest callback and cleans up whenever its delay changes.
import { useEffect, useLayoutEffect, useRef } from "react";
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
/** Repeatedly invokes the latest callback; a null delay disables the interval. */export const useInterval = ( callback: (this: void) => void, intervalMs: number | null,): void => { if ( intervalMs !== null && (!Number.isFinite(intervalMs) || intervalMs < 0) ) { throw new RangeError( "intervalMs must be null or a finite, non-negative number", ); }
const callbackRef = useRef(callback);
useIsomorphicLayoutEffect(() => { callbackRef.current = callback; }, [callback]);
useEffect(() => { if (intervalMs === null) return;
const timer = setInterval(() => { Reflect.apply(callbackRef.current, undefined, []); }, intervalMs);
return () => { clearInterval(timer); }; }, [intervalMs]);};Download
Section titled “Download”wget -O src/hooks/useInterval.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/react-hooks/useInterval.tstemp_dir="$(mktemp -d)" && bunx degit jrTilak/lazykit/registry/react-hooks "$temp_dir" && mv "$temp_dir/useInterval.ts" src/hooks/useInterval.ts && rm -r "$temp_dir"Examples
Section titled “Examples”import { useState } from "react";
import { useInterval } from "./useInterval";
export const ElapsedSeconds = () => { const [seconds, setSeconds] = useState(0); const [running, setRunning] = useState(true);
useInterval( () => setSeconds((value) => value + 1), running ? 1_000 : null, );
return ( <button type="button" onClick={() => setRunning((value) => !value)}> {seconds}s · {running ? "Pause" : "Resume"} </button> );};callback(() => void) — Latest function to invoke on every tick.intervalMs(number | null) — Finite, non-negative interval.nullpauses the interval;0is valid.
Returns
Section titled “Returns”void. Callback changes do not recreate the interval. Delay changes and unmounting clean up the previous timer.