Even after adding if else statment inside setInterval() function to clear the interval once my time variable reaches 0, the setInterval() is still running with negative time value.
Please specify what the problem is and provide some solution for the same.
Here is my code for count-down timer component:
import { useEffect, useState, useRef } from "react";
export const Timer = () => {
const [time, setTime] = useState(10000);
const [pause, setPause] = useState(false);
let timerRef = useRef(null);
useEffect(() => {
startTimer();
return () => clearInterval(timerRef.current);
}, []);
const stopTimer = () => {
clearInterval(timerRef.current);
setPause(true);
};
const startTimer = () => {
setPause(false);
timerRef.current = setInterval(() => {
if (time <= 0) {
clearInterval(timerRef.current);
setPause(true);
}
else{
setTime((prevTime) => prevTime - 1000);
}
}, 1000);
};
const getFormattedText = (time) => {
const SECONDS = 1000;
const MINUTES = 60 * SECONDS;
const HOURS = 60 * MINUTES;
const DAYS = 24 * HOURS;
const days = Math.floor(time / DAYS);
const hours = Math.floor((time % DAYS) / HOURS);
const minutes = Math.floor((time % HOURS) / MINUTES);
const seconds = Math.floor((time % MINUTES) / SECONDS);
return (
<div className="countdown-timer">
{days < 10 ? `0${days}` : days} : {hours < 10 ? `0${hours}` : hours} :{" "}
{minutes < 10 ? `0${minutes}` : minutes} :{" "}
{seconds < 10 ? `0${seconds}` : seconds}
</div>
);
};
return (
<>
{getFormattedText(time)}
<div>
<button onClick={pause ? startTimer : stopTimer}>
{!pause ? "Stop" : "Start"} Timer
</button>
</div>
</>
);
};
export default Timer;
Here is the output after 0 second:
3
Answers
you click event is likely automatically firing
The
setInterval
inside yourstartTimer
is receiving only a static value oftime
– the initial10000
stays as it is. Even if yousetTime
, the updatedtime
is only reflected outside thesetInterval
and not within, becausesetInterval
executes on its own scope and context. So theelse
part is executed always, giving you negative values.To fix, you will need a
useEffect
that will respond to the change due tosetTime
Replace yourstartTimer
method with the below code,your initial
startTimer
callback is split into usinguseEffect
wherein it can listen and respond to change in statetime
due tosetTime
. Please refer https://react.dev/reference/react/useEffect#updating-state-based-on-previous-state-from-an-effect for further refinement of the solution.The problem lies in the closure created for the time state inside the
setInterval() callback. When your setInterval() function is set up, it
captures the value of time at that moment, and subsequent updates to
time aren’t reflected within that closure.
The current state of time is always checked against 0 correctly,
preventing it from going negative. The interval is cleared correctly
when the time reaches 0 or less. The setPause(true) action is
correctly called within the context where prevTime is guaranteed to be
the latest value of time.