'useState hook does not update inside setTimeout function

this code must increase the "count" value gradually from 0 to 600. And each time the "count" value changes it must be logged into the console. But instead, I get the "count" value consoled from 6 to 15 times. So the "count" value updates scarcely 20 times instead of desired 600. What might be the problem with this code?

const [count, setCount] = useState(0);

useEffect(() => {
  console.log(count);
}, [count]);

const startCount = () => {
  for(let i = 0, i < 600, i++)
    setTimeout(() => {
      setCount(prev => prev + 1);
    }, i);
  };
};


Solution 1:[1]

  1. When you set new state component re-renders
  2. Every time the component re-renders it starts a new loop.
  3. loop sets a new state, which will cause re-render which will start step-1.

This will cause an infinite loop.

You can achieve a count from 0-n like below.

export default function App() {
  const [count, setCount] = React.useState(0);

  React.useEffect(() => {
    console.log(count);
    if (count <= 600) {
      setTimeout(() => {
        setCount((prev) => prev + 1);
      }, count);
    }
  }, [count]);

  return (
    <div>
      <h1>Hello StackBlitz! {count}</h1>
      <p>Start editing to see some magic happen :)</p>
    </div>
  );
}

Solution 2:[2]

useState is operating as intended. See "Batching of state updates"

Your startCount function is setting things up to update the count every millisecond for the next 600 milliseconds. The state is updating, but probably much faster than you intended (you said "gradually" so I suspect you intended one update per second). And since it's updating faster than your browser is going to be able to re-render your component, the state updates are being batched. Since your console.log is tied to the rendering cycle via useEffect, it only runs when your component re-renders.

By the way, setInterval would be much better to use than setTimeout here since it's intended for repeatedly calling a function at some regular time interval.

const [isCounting, setCounting] = useState(false)
const startCounting = () => setCounting(true)

const [count, setCount] = useState(0)

// the function within will run when the component mounts,
// and when `isCounting` changes
useEffect(() => {
  // don't want to do anything if we're not "counting"
  if (isCounting) {
    // set up the periodic function call here
    const interval = setInterval(() => {
      setCount(prev => {
        if (prev < 600) {
          // "counting" increment
          return prev + 1
        } else {
          // turn off the periodic call if we reach 600
          clearInterval(interval)
          return 600
        }
      })
    }, 500)

    // if `isCounting` changes again, react will call the "cleanup" function
    // that you return from useEffect, before calling the new useEffect function
    return () => clearInterval(interval)
  }
}, [isCounting])

Solution 3:[3]

Problem is that the for loop executes 600 times & batches 600 state updates within the next interval which is i.

If you increase the setTimeout interval to 2000 you would see a 2 second delay & then there would be a quick 600 state updates.

const {useState, useEffect} = React;

function App() {
  const [count, setCount] = useState(0);
  const interval = 2000;
  useEffect(() => {
    console.log(count);
  }, [count]);

  const startCount = () => {
    for (let i = 0; i < 600; i++) {
      setTimeout(() => {
        setCount((prev) => prev + 1);
      }, interval);
    }
  };

  return (
    <div className="App">
      <h3>{count}</h3><button onClick={startCount}>Start (interval {interval})</button>
    </div>
  );
}

ReactDOM.render(
      <App/>,
      document.getElementById("react")
    );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
 <div id="react">

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rahul Sharma
Solution 2 Dylan
Solution 3 Aseem Gautam