Reputation: 15
I want to create a time of 60s on react using hooks useState
and useEffects
This is what i am doing
import '../assets/css/timer.css'
import { useState, useEffect } from 'react'
const Timer = () =>{
const [ time, setTime ] = useState(0);
useEffect(()=>{
if(time!==60){
setInterval(()=>{
setTime(prevTime => prevTime+1) ;
}, 1000);
}
}, [])
return(
<>
<div className="circular">
<div className="inner"></div>
<div className="outer"></div>
<div className="numb">
{time} // Place where i am displaying time
</div>
<div className="circle">
<div className="dot">
<span></span>
</div>
<div className="bar left">
<div className="progress"></div>
</div>
<div className="bar right">
<div className="progress"></div>
</div>
</div>
</div>
</>
)
}
export default Timer
Timer is not stopping. It continues to go on for ever. I have tried this too
useEffect(()=>{
setInterval(()=>{
if(time!==60)
setTime(prevTime => prevTime+1) ;
}, 1000);
}, [])
Can some please explain where things are going wrong.
Upvotes: 0
Views: 372
Reputation: 66
You have to clear setTimeout in Unmount state
for functional component
// Funtional component
useEffect(()=>{
const i = setInterval(() => {
setTime(prevTime => {
if (prevTime !== 60) return prevTime+1;
clearInterval(i);
return prevTime;
});
}, 1000);
return () => clearInterval(i);
}, [])
For class component
// Class component
componentWillUnmount() {
this.clearInterval()
}
Upvotes: 0
Reputation: 12919
You are close to having it working with your first attempt, but there are a few problems.
The main problem is that you pass an empty dependency array, meaning it will only run on the first render and not be updated on successive renders. Secondly you don't provide a return or 'clean up' meaning the interval is never cleared.
useEffect(() => {
if (time < 60) {
const timer = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
return () => clearInterval(timer);
}
}, [time])
Here we pass time
in the dependency array and conditionally set the interval if time
is less than your end time, 60 in your case but I shortened it to 5 so that you can see it stop. We also pass a return
callback that will clear the interval at the end of each render cycle.
With this set up every time the setInterval updates the time
state the useEffect will clear the interval at the end of the previous render, and then re-run in the current render setting the interval again if time is less than the limit.
The advantage of using the React render cycle this way, instead of clearing the interval in the interval callback, is that it gives you granular control of your timeout – allowing you to easily add further checks or extend/shorten the time based on other state values.
const { useState, useEffect } = React;
const Timer = () => {
const [time, setTime] = useState(0);
useEffect(() => {
if (time < 5) {
const timer = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
return () => clearInterval(timer);
}
}, [time])
return (
<div className="circular">
<div className="numb">
{time}
</div>
</div>
)
}
ReactDOM.render(
<Timer />,
document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Upvotes: 0
Reputation: 64657
useEffect(..., [])
will only run once, so time
inside of it will never update. So you need to check prevTime
inside of the setTime
function, and then only increment if it's not 60. If it is, you should clear the interval, and then you should clear the interval in the cleanup of useEffect:
useEffect(()=>{
const i = setInterval(() => {
setTime(prevTime => {
if (prevTime !== 60) return prevTime+1;
clearInterval(i);
return prevTime;
});
}, 1000);
return () => clearInterval(i);
}, [])
Upvotes: 1