Coded Geekery

BlogAboutContact
BlogAboutContact

useInterval

A React-friendly alternative to window.setInterval

useInterval

Intervals can be pretty tricky in React; if you're not careful, you'll wind up with a "stale" interval, one which can't access current values of state or props.

This hook allows us to sidestep that confusion, and it also gives us a superpower: we can modify the delay without having to worry about stopping and starting the interval.

function useInterval(callback, delay) { const intervalId = React.useRef(null); const savedCallback = React.useRef(callback); React.useEffect(() => { savedCallback.current = callback; }); React.useEffect(() => { const tick = () => savedCallback.current(); if (typeof delay === 'number') { intervalId.current = window.setInterval(tick, delay); return () => window.clearInterval(intervalId.current); } }, [delay]); return intervalId.current; }

Why?

When dealing with a server-side rendered application (through frameworks like Gatsby or Next, or any sort of SSR setup), it can be useful to know whether you're rendering on the server or the client.

For example: If you don't have a dynamic backend (eg. a Gatsby app), you won't know whether the user is authenticated or not. In that first pre-render, you should render a blank spot. When the cient renders, you can fill it in, either with the user's avatar or a "Sign in" link.

Usage

  • Basic Usage

  • useInterval(() => { console.log('I fire every second!'); }, 1000);
  • More Advanced Usage

  • Here's an example of a "Stopwatch" component. useInterval lets us track how much time has elapsed since the user has started the stopwatch.

    const Stopwatch = () => { const [status, setStatus] = React.useState('idle'); const [timeElapsed, setTimeElapsed] = React.useState(0); useInterval( () => { setTimeElapsed((timeElapsed) => timeElapsed + 1); }, status === 'running' ? 1000 : null ); const toggle = () => { setTimeElapsed(0); setStatus((status) => (status === 'running' ? 'idle' : 'running')); }; return ( <> Time Elapsed: {timeElapsed} second(s) <button onClick={toggle}> {status === 'running' ? 'Stop' : 'Start'} </button> </> ); };

    Wait there's more!!!

    Let's expand on that with useRandomInterval, which is like setInterval, but more random.

    // Utility helper for random number generation const random = (min, max) => Math.floor(Math.random() * (max - min)) + min; const useRandomInterval = (callback, minDelay, maxDelay) => { const timeoutId = React.useRef(null); const savedCallback = React.useRef(callback); React.useEffect(() => { savedCallback.current = callback; }); React.useEffect(() => { let isEnabled = typeof minDelay === 'number' && typeof maxDelay === 'number'; if (isEnabled) { const handleTick = () => { const nextTickAt = random(minDelay, maxDelay); timeoutId.current = window.setTimeout(() => { savedCallback.current(); handleTick(); }, nextTickAt); }; handleTick(); } return () => window.clearTimeout(timeoutId.current); }, [minDelay, maxDelay]); const cancel = React.useCallback(function () { window.clearTimeout(timeoutId.current); }, []); return cancel; };
  • Usage

  • This example uses the hook to create a "laggy" clock (a clock that only updates once every few seconds):

    function LaggyClock() { // Update between every 1 and 4 seconds const delay = [1000, 4000]; const [currentTime, setCurrentTime] = React.useState(Date.now); useRandomInterval(() => setCurrentTime(Date.now()), ...delay); return <>It is currently {new Date(currentTime).toString()}.</>; }

    Coded Geekery

    © 2021 Roger Stringer. All rights reserved.

    TwitterGitHubInstagram

    Stuff

    HomeAbout MeContact Me

    Not Playing

    Spotify