1

I want to have a countdown timer that counts from 60 to zero when the stage of program reaches two. Here's my code:

const [timer, setTimer] = useState(60)
useEffect(() => {
        if (stage === 2) {
            const interval = setInterval(() => {
                console.log(timer)
                setTimer(timer - 1)
                if (timer === 1) {
                    clearInterval(interval)
                }
            }, 1000)
        }
    }, [stage])

and i have a div like below that just shows the counter value

<div>{timer}</div>

in my setInterval, when i use console.log(timer) it always prints out 60. But inside the div, the value starts with 60 and in the next second it will always be 59. What am i doing wrong here?

Ardalan
  • 723
  • 9
  • 26
  • 1
    you need to set the state and use the state so that component renders. – Jayanti Lal Feb 24 '21 at 10:06
  • 1
    You are `console.log`ing before you `-1` from the timer, so even if it was synchronous it would print out 60. But as `setTimer` is asynchronous, you can't rely on the log ever showing the new value immediately, you would need an effect listening for changes to `timer`. – DBS Feb 24 '21 at 10:06
  • i know that. All i'm saying is, why the state won't get updated? – Ardalan Feb 24 '21 at 10:22

1 Answers1

2

You have closure on time === 60 value, use functional update instead.

For the same reason, you should have another useEffect for canceling the interval:

const intervalId = useRef();

useEffect(() => {
  if (stage === 2) {
    intervalId.current = setInterval(() => {
      setTimer((prevTimer) => prevTimer - 1);
    }, 1000);
  }
}, [stage]);

useEffect(() => {
  console.log(timer);
  if (timer === 1) {
    clearInterval(intervalId.current);
  }
}, [timer]);

Check similar question: setInterval counter common mistakes.

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118