I'm learning to react hooks. I write a count-down component. useEffect
makes me confused.
when the timer turns to 00:00, it can't show 00:00, just turn to 00:10. anyone can help?
below is a demo.
export default function App() {
const [timer, setTimer] = useState(dayjs().minute(0).second(10));
const [status, setStatus] = useState(true);
const handleReset = () => {
setStatus(true);
setTimer(dayjs().minute(0).second(10));
};
const handleStartStop = () => {
setStatus((pre) => !pre);
};
useEffect(() => {
setTimer((pre) => {
if (pre.format("mm:ss") === "00:00") {
return dayjs().minute(0).second(10);
}
return pre;
});
}, [timer]);
useEffect(() => {
if (!status) {
const intervalId = setInterval(() => {
setTimer((pre) => {
return pre.subtract(1, "second");
});
}, 1000);
return () => {
clearInterval(intervalId);
};
}
}, [status]);
return (
<>
<div id="timer-label">
count down
<div id="time-left">{timer.format("mm:ss")}</div>
<div>
<button id="start_stop" onClick={() => handleStartStop()}>
{status ? "start" : "stop"}
</button>
<button id="reset" onClick={handleReset}>
reset
</button>
</div>
</div>
</>
);
}
When you split and use 2 useEffect
hooks, it seems there is some clock skew between the effects and the timing of checking for "00:00" and resetting is out of sync with the interval.
useEffect(() => console.log('----- render -----'));
useEffect(() => {
setTimer((pre) => {
console.log('effect 2', pre.format("mm:ss"))
if (pre.format("mm:ss") === "00:00") {
return dayjs().minute(0).second(10);
}
return pre;
});
}, [timer]);
useEffect(() => {
if (!status) {
const intervalId = setInterval(() => {
setTimer((pre) => {
console.log('effect 1', pre.format("mm:ss"))
return pre.subtract(1, "second");
});
}, 1000);
return () => {
clearInterval(intervalId);
};
}
}, [status]);
These logs yield:
----- render -----
effect 1 00:10
----- render -----
effect 2 00:09
effect 1 00:09
----- render -----
effect 2 00:08
effect 1 00:08
----- render -----
effect 2 00:07
----- render -----
effect 2 00:06
effect 1 00:07
----- render -----
effect 2 00:05
effect 1 00:06
----- render -----
effect 2 00:04
effect 1 00:05
----- render -----
effect 2 00:03
effect 1 00:04
----- render -----
effect 2 00:02
effect 1 00:03
----- render -----
effect 2 00:01
effect 1 00:02
----- render -----
effect 2 00:00 // <-- this one is also not in sync
----- render -----
effect 2 00:10 // <-- immediately reset
effect 1 00:01
This issue and skew is resolved by placing all the logic in a single useEffect
.
useEffect(() => {
if (!status) {
const intervalId = setInterval(() => {
setTimer((pre) => {
if (pre.format("mm:ss") === "00:00") {
return dayjs().minute(0).second(10);
}
return pre.subtract(1, "second");
});
}, 1000);
return () => {
clearInterval(intervalId);
};
}
}, [status]);