useInterval
const 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?
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.
Usage
useInterval(() => {
console.log('I fire every second!');
}, 1000);
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>
</>
);
};
Let's expand on that with useRandomInterval
, which is like setInterval
, but more random.
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;
};
This example uses the hook to create a "laggy" clock (a clock that only updates once every few seconds):
const 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()}.</>;
}