R. Shoemaker
R. Shoemaker

Reputation: 75

setTimeout inside for loop in React component never stops iterating

I am trying to create a simple counter component in React. It should start at 0, then iterate up to the passed in number. Here is the code I currently have:

const Counter = ({ number }) => {
  const [currentNumber, setCurrentNumber] = useState(0);

  for (let i = 0; i < number; i++) {
    setTimeout(() => setCurrentNumber(currentNumber + 1), 2000);
  }

  return <span>{currentNumber}</span>;
};

export default Counter;

What happens when I run this, is it basically keeps counting forever. currentNumber never stops incremementing, even once the number has been reached.

Upvotes: 0

Views: 151

Answers (3)

Mushroomator
Mushroomator

Reputation: 9178

You should add currentNumber as a dependency for useEffect(). This way useEffect() will get triggered every second and a new timeout will be registered but only as long as currentNumber is smaller than number.

const Counter = ({ number }) => {
  const [currentNumber, setCurrentNumber] = React.useState(0);

  React.useEffect(() => {
    if(currentNumber < number) setTimeout(() => setCurrentNumber(currentNumber + 1), 1000);
  }, [currentNumber]);

  return <span>{currentNumber}</span>;
};

ReactDOM.render(<Counter number={20}/>, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Upvotes: 2

Phillip
Phillip

Reputation: 184

Please try this code:

const [currentNumber, setCurrentNumber] = useState(0);

useEffect(() => {
    if (currentNumber >= number) return;
    setTimeout(() => {
        setCurrentNumber(currentNumber + 1);
    }, 1000);
}, [currentNumber]);

return <span>{currentNumber}</span>;

Upvotes: 0

debel_vegetarianec
debel_vegetarianec

Reputation: 44

I ran the code and it's getting stuck. Reason:

You're scheduling 5 timers which will all update currentNumber to currentNumber + 1. currentNumber is 0, so it will update number times to 1 after 2 seconds. You can approach this with an interval or a useEffect:

useEffect(() => {
  if (currentNumber < number)
    setTimeout(() => setCurrentNumber(n => n + 1), 2000)
}, [number, currentNumber])

If you're using an interval, make sure you return a cleanup callback from useEffect, like that:

useEffect(() => {
  const id = useInterval(...)
  return () => clearInterval(id)
}, [...])

Upvotes: 0

Related Questions