Why setInterval/setTimeout gets an old state or reference in react components. Avoid reading states inside closure.
Updating state directly within a setInterval callback in React components can lead to unexpected behavior and potential issues. The primary reason to avoid this practice is due to closures and how they interact with state updates in React.
When setInterval is used, it creates a closure over the current state values at the time the interval is set. Even if the state is updated later, the callback function within setInterval will still reference the original state values captured in the closure. This can result in the component not re-rendering with the latest state, leading to stale or incorrect UI updates.
To avoid these problems, it’s recommended to use the functional form of setState when updating state within a setInterval callback. This approach ensures that you're working with the most recent state value, as it receives the previous state as an argument.
import React, { useState, useEffect, useRef } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return Count: {count};
}
But, What if my counter is conditional, I need to start counter on a button click. and stop at value "10"
Or
Oh no!, it in not stopping at 10!, count value inside setInterval is always "0". As i mentioned earlier it is due to the initial values used in closure.
How do we fix this?
Use setTimeout instead of setInterval.
Read state outside closure.
use setTimeout in combination with React's re-rendering to mimic the behavior of setInterval. This approach is often preferred because it gives you more control over the timing and avoids some of the pitfalls of setInterval, such as overlapping executions if the callback takes too long and reading old render values.
here is an example.
✅ Recommended: setTimeout (recursive approach)
Why it's better:
More control: You can easily adjust the timing between executions dynamically.
Avoids overlapping: Unlike setInterval, which runs on a fixed schedule regardless of how long the callback takes, setTimeout ensures the next call only happens after the previous one completes.
Cleaner logic in React: It fits naturally with React's re-render cycle and useEffect dependencies.
React-friendly for most use cases
Best for:
Controlled, step-by-step updates (like counting to a specific number).
Situations where you might want to pause, resume, or adjust timing dynamically.
Do not read a state or call methods inside closure functions.