I'm trying to set a countdown timer using useRef, so that I prevent re-rendering. I want the countdown timer to start counting down once the start button is clicked, then once it gets to 0, stop.
I'm a beginner, so I'm not sure what I'm doing wrong. I saw this Start the timer when button is clicked in React.Js and I tried implementing it and adding a useRef, but it's not working. Please help, thanks in advance.
const {useState, useRef, useEffect} = React;
const Timer = () => {
const [secondsLeft, setSecondsLeft] = useState(10);
const [start, setStart] = useState(false);
let intervalRef = useRef();
const decreaseSeconds = () => setSecondsLeft((prev) => prev - 1);
useEffect(() => {
intervalRef.current = setInterval(decreaseSeconds, 1000);
return () => clearInterval(intervalRef.current);
}, []);
cosnt handleClick = () => {
let timer = null;
if (start) { // if start button is clicked...
setStart(true);
intervalRef.current = setInterval(decreaseSeconds, 1000); // start timer again
} else {
clearInterval(intervalRef.current); // stop time if button isn't clicked
}
};
return (
<div>
<div>{secondsLeft}</div>
<button onClick={handleClick}>{start ? "Start" : "Cancel"}</button>
</div>
);
}
ReactDOM.render(
<Timer />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
CodePudding user response:
You need to clear interval when secondsLeft
value get 0.
Everytime whenever decreaseSeconds function get called check for current value and clear interval on desired condition.
const decreaseSeconds = () => {
setSecondsLeft((prev) => {
if (prev === 0) {
clearInterval(intervalRef);
setStart(false);
return 0;
}
else {
return prev - 1;
}
});
}
The clearInterval you used under useEffect get called when this component get unmout/destroy. As this is a Good Practice to add clear timeout/interval function in useEffect so that if component get's unmount timeout/interval function get clear otherwise it will be running in backgroud and cause errors.
Check useEffect Cleanup function
CodePudding user response:
Adding to what Rohit said in his answer, here's a working example. You could use a ref to store the timer, but it's not needed in this case as you have a closure within the useEffect. Note that the dependency array includes the start
state, so this useEffect will run each time start is updated.
const Timer = () => {
const [secondsLeft, setSecondsLeft] = React.useState(10);
const [start, setStart] = React.useState(false);
const decreaseSeconds = () => {
setSecondsLeft((prev) => {
if (prev > 0) {
return --prev
} else {
setStart(false)
return 10
}
})
};
React.useEffect(() => {
let timer
if (start) {
timer = setInterval(decreaseSeconds, 1000)
} else {
clearInterval(timer)
}
return () => clearInterval(timer)
}, [start])
const handleClick = () => setStart((prev) => !prev);
return (
<div>
<div>{secondsLeft}</div>
<button onClick={handleClick}>{start ? 'Cancel' : 'Start'}</button>
</div>
)
}
ReactDOM.createRoot(document.getElementById("root")).render(<Timer />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>