UNPKG

@restart/hooks

Version:

A set of utility and general-purpose React hooks.

89 lines (83 loc) 2.52 kB
import { useEffect } from 'react'; import useCommittedRef from './useCommittedRef'; /** * Creates a `setInterval` that is properly cleaned up when a component unmounted * * ```tsx * function Timer() { * const [timer, setTimer] = useState(0) * useInterval(() => setTimer(i => i + 1), 1000) * * return <span>{timer} seconds past</span> * } * ``` * * @param fn an function run on each interval * @param ms The milliseconds duration of the interval */ /** * Creates a pausable `setInterval` that is properly cleaned up when a component unmounted * * ```tsx * const [paused, setPaused] = useState(false) * const [timer, setTimer] = useState(0) * * useInterval(() => setTimer(i => i + 1), 1000, paused) * * return ( * <span> * {timer} seconds past * * <button onClick={() => setPaused(p => !p)}>{paused ? 'Play' : 'Pause' }</button> * </span> * ) * ``` * * @param fn an function run on each interval * @param ms The milliseconds duration of the interval * @param paused Whether or not the interval is currently running */ /** * Creates a pausable `setInterval` that _fires_ immediately and is * properly cleaned up when a component unmounted * * ```tsx * const [timer, setTimer] = useState(-1) * useInterval(() => setTimer(i => i + 1), 1000, false, true) * * // will update to 0 on the first effect * return <span>{timer} seconds past</span> * ``` * * @param fn an function run on each interval * @param ms The milliseconds duration of the interval * @param paused Whether or not the interval is currently running * @param runImmediately Whether to run the function immediately on mount or unpause * rather than waiting for the first interval to elapse * */ function useInterval(fn, ms, paused = false, runImmediately = false) { let handle; const fnRef = useCommittedRef(fn); // this ref is necessary b/c useEffect will sometimes miss a paused toggle // orphaning a setTimeout chain in the aether, so relying on it's refresh logic is not reliable. const pausedRef = useCommittedRef(paused); const tick = () => { if (pausedRef.current) return; fnRef.current(); schedule(); // eslint-disable-line no-use-before-define }; const schedule = () => { clearTimeout(handle); handle = setTimeout(tick, ms); }; useEffect(() => { if (runImmediately) { tick(); } else { schedule(); } return () => clearTimeout(handle); }, [paused, runImmediately]); } export default useInterval;