Skip to content

useInterval

Edit

Repeatedly invokes the latest callback and cleans up whenever its delay changes.

useInterval.ts
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]);
};
Terminal
wget -O src/hooks/useInterval.ts https://raw.githubusercontent.com/jrTilak/lazykit/HEAD/registry/react-hooks/useInterval.ts
useInterval.example.tsx
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. null pauses the interval; 0 is valid.

void. Callback changes do not recreate the interval. Delay changes and unmounting clean up the previous timer.