I am trying to create a counter which decreases every second after clicking on it. The initial state of the clock is 25:00 which is rendered as {timerMinutes}:{timerSeconds}
in the Timer component. I am decrementing the previous number of seconds by 1 to calculate the new state and based on this new state, the new minutes and seconds are calculated.
However, the clock goes back to 24:59 after 24:00 and keeps looping in that range forever.
Here is the code –
const Clock = () => {
const [timerMinutes, setTimerMinutes] = useState(25);
const [timerSeconds, setTimerSeconds] = useState(0);
const startStop = () => {
setInterval(()=>{
setTimerMinutes(prevMins => {
let time = prevMins*60;
setTimerSeconds(prevSecs => {
time+=prevSecs; // Total seconds of the last state
time-=1; // The next state
return time%60; // The number of seconds in the next state
})
return Math.floor(time/60); // The number of mins in next state
});
}, 1000);
}
const Timer = ({timerMinutes, timerSeconds}) => {
return (
<div onClick={startStop}>
<div id="timer-label">Session</div>
<div id="time-left">{timerMinutes<10? '0'+timerMinutes:timerMinutes}:{timerSeconds<10? '0'+timerSeconds:timerSeconds}</div>
</div>
);
}
return (
<div>
<div>25 + 5 Clock</div>
<Timer timerMinutes={timerMinutes} timerSeconds={timerSeconds}/>
</div>
);
}
Is this related to some asynchronous behavior of JS or the setInterval() function? Please dry run to see the results and correct if there are any problems in the code. Thanks!
2
Answers
I think you’re making it way harder on yourself by using two states that are that closely connected. Something like this seems to work:
However, you should probably not trust setInterval to keep proper timing. What you can do for a better result is probably to keep only the starting point of the timer (
Date.now()
) and calculate the amount of time remaining at each interval.The Timer component should probably not be defined inside the other one, it’s usually a bad pattern because it will unmount and remount at each render.
Also, the fact that you are not cleaning the interval will probably cause trouble.
Using modulus / math function isn’t needed.
You should also keep the return value of
setInterval
so you can callclearInterval
if you want to pause the timer.Then use a
useEffect
to set the interval if some value changes.Little demo based on your code: