I’m trying to implement the countdown timer with React’s hooks. I knew, we have couple of solutions out there and thanks for that. My question is NOT How to implement timer with React's hooks
BUT What is the problem with my approach
Here is my code so far.
import React, { useEffect, useState } from "react";
const Timer = () => {
const [minutes, setMinutes] = useState(29);
const [seconds, setSeconds] = useState(59);
useEffect(() => {
const secondInterval = setInterval(() => {
setSeconds((prev) => (prev - 1 < 0 ? 59 : prev - 1));
}, 1000);
const minuteInterval = setInterval(() => {
setMinutes(minutes - 1);
}, 60000);
return () => {
clearInterval(secondInterval);
clearInterval(minuteInterval);
};
});
return (
<div>
{minutes === 0 && seconds === 0 ? null : (
<h1>
{" "}
{minutes}:{seconds < 10 ? `0${seconds}` : seconds}
</h1>
)}
</div>
);
};
export default Timer;
Basically, I just set 2 interval for every render with useEffect
no depths. But the second setTimeInterval
does not run and the minutes show 29
everytime.
Thanks in advance !
3
Answers
When the
useEffect
is called (by setting the seconds for example), your intervals are cleared. This means that the minutes interval is never executed. Add an empty dependencies array to theuseEffect
, and set theminutes
using an updater function.However, since your
seconds
andminutes
are out of sync – theseconds
starts from 50, and the minutes interval is 60,000 ms (60 sec), you’ll only see theminutes
changing 10 seconds after theseconds
counter reaches 0.You can solve this by using a second
useEffect
that sets theminutes
wheneverseconds
reaches 59:Check this article about how javascript timers work
So, as JavaScript is single threaded and can only execute one line at a time, the second
setTimeInterval
for minutes is being blocked by firstsetTimeInterval
used for seconds. It is getting queued up to be executed later, and sinceuseEffect
is being called for every second interval, this queue is getting cleared each time and the code never actually reaches to the secondsetTimeInterval
Main issue with your code is missing dependency array. Without dependency array you’re adding and removing intervals every render thus your 59 seconds will never happen.
You may want to try console logging like this to make it more clear what is error, just set some variable i globally and log it without dependency array:
if you do same thing with dependency array you will see that it actually only prints once which would be desired behaviour.
you could potentially add second timer to be 600ms and then only that timer will run as your component will rerender every 600ms.
With empty dependency array your code inside useEffect will only run once, if you wish to include some dependency (for useEffect to run once that value changes just add it to array)