DEV Community

sojin antony
sojin antony

Posted on • Originally published at Medium on

4

Why setInterval/setTimeout gets an old state or reference in react components.

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 <div>Count: {count}</div>;
}
Enter fullscreen mode Exit fullscreen mode

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.

ACI image

ACI.dev: Best Open-Source Composio Alternative (AI Agent Tooling)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Star our GitHub!

Top comments (4)

Collapse
 
nevodavid profile image
Nevo David

wow, reminds me how many bugs i've had from stale state in closures tbh - you ever get paranoid about using setinterval for counters after stuff like this?

Collapse
 
sojinantony01 profile image
sojin antony

For few cases i still using setInterval, may i need to go back and change it back to timeout

Collapse
 
juan_labrada_42e0d23118f4 profile image
Juan Labrada

It be clearTimeout, instead of clearInterval in the recommended example. BTW, excellent article!!

Collapse
 
sojinantony01 profile image
sojin antony

Yup thats correct.
Thank you

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping