Reputation: 651
I am trying to create a Pomodoro timer in ReactJS. I am having trouble having the timer to stop it's countdown.
PomView.js
const PomView = () => {
const [timer, setTimer] = useState(1500) // 25 minutes
const [start, setStart] = useState(false)
var firstStart = useRef(true)
var tick;
useEffect( () => {
if (firstStart.current) {
console.log("first render, don't run useEffect for timer")
firstStart.current = !firstStart.current
return
}
console.log("subsequent renders")
console.log(start)
if (start) {
tick = setInterval(() => {
setTimer(timer => {
timer = timer - 1
console.log(timer)
return timer
}
)
}, 1000)
} else {
console.log("clear interval")
clearInterval(tick);
}
}, [start])
const toggleStart = () => {
setStart(!start)
}
const dispSecondsAsMins = (seconds) => {
// 25:00
console.log("seconds " + seconds)
const mins = Math.floor(seconds / 60)
const seconds_ = seconds % 60
return mins.toString() + ":" + ((seconds_ == 0) ? "00" : seconds_.toString())
}
return (
<div className="pomView">
<ul>
<button className="pomBut">Pomodoro</button>
<button className="pomBut">Short Break</button>
<button className="pomBut">Long Break</button>
</ul>
<h1>{dispSecondsAsMins(timer)}</h1>
<div className="startDiv">
{/* event handler onClick is function not function call */}
<button className="startBut" onClick={toggleStart}>{!start ? "START" : "STOP"}</button>
{start && <AiFillFastForward className="ff" onClick="" />}
</div>
</div>
)
}
export default PomView
Although the clearInterval
runs in the else portion of useEffect, the timer continues ticking. I am not sure if it is because of the asynchronous setTimer method in useEffect. I would like to know what the problem is with the code I have written.
Upvotes: 3
Views: 13616
Reputation: 203218
You store the timer ref in tick
, but each time the component rerenders the tick
value from the previous render is lost. You should also store tick
as a React ref.
You are also mutating the timer
state.
setTimer((timer) => {
timer = timer - 1; // mutation
return timer;
});
Just return the current value minus 1: setTimer((timer) => timer - 1);
Code
const PomView = () => {
const [timer, setTimer] = useState(1500); // 25 minutes
const [start, setStart] = useState(false);
const firstStart = useRef(true);
const tick = useRef(); // <-- React ref
useEffect(() => {
if (firstStart.current) {
firstStart.current = !firstStart.current;
return;
}
if (start) {
tick.current = setInterval(() => { // <-- set tick ref current value
setTimer((timer) => timer - 1);
}, 1000);
} else {
clearInterval(tick.current); // <-- access tick ref current value
}
return () => clearInterval(tick.current); // <-- clear on unmount!
}, [start]);
...
};
Upvotes: 2
Reputation: 1050
useEffect( () => {
const tick= setInterval(fun, 1000);
return ()=>{
clearInterval(tick);
}
}, [])
useEffect has it's own release way.
Upvotes: 3