So I am making a countdown component for 1 of my projects to see the time left before the user can do something again. I have the time difference in seconds, and send the seconds to my countdown component. This is what my countdown component looks like.
So for right now I am not even using the time difference for testing purposes I just did 5000 seconds.
let [timerClock, setTimerClock] = useState(5000);
useEffect(() => {
setInterval(function() {
console.log("minus: ", timerClock)
setTimerClock(timerClock - 1);
}, 1000)
}, [timerClock]);
So for the first like 10 seconds everything works correctly, but after the first couple seconds it starts bugging out and doesn't work correctly. It will go up and down within the same numbers and start going a lot faster between. So say it starts at 5000, once it reaches like 4990 it will start bouncing between 4990, 4991, 4993 ect it goes up before it subtracts for some reason and just all out glitchy. Am I doing something wrong? It looks to me like it should work just not understanding whats going on.
Here is an image of my counsole, as you can see it's not counting down properly this time it messed up even quicker.
CodePudding user response:
You have to remove timerClock
from the useEffect dependencies Array.
Everytime a interval tick occurs you call setTimerClock
which causes the effect to be executed again.
When that happens a new interval is registered.
So after a few seconds you already have several intervals running at once.
All are updating timerClock and new intervals get added.
I would suggest you read up on when to use the dependencies array: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
Also note this section:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
CodePudding user response:
So a big issue here is that you you are not cleaning up your Interval when the useEffect runs again. This effectively is creating a bunch of setIntervals (all of them will setTimerClock to what the value of timerClock was when the effect ran) and will lead to excessive use of memory. Effectively every time you update timerClock you create another interval that is running seperate from the previous.
useEffect(() => {
const timer = setInterval(function() {
console.log("minus: ", timerClock)
setTimerClock(timerClock - 1);
}, 1000)
return () => { // this runs as the clean up function for the useEffect
clearInterval(timer)
}
}, [timerClock]);
- every time the state variable timerClock changes this useEffect runs again.
- when timerClock gets changed from setTimerClock() the return function will run to clean up the interval and then run the new Effect with the new timerClock value creating a new Interval.
- effectively you should be using setTimeout with clearTimeout like the code below.
useEffect(() => {
const timer = setTimeout(function() {
console.log("minus: ", timerClock)
setTimerClock(timerClock - 1);
}, 1000)
return () => { // this should work flawlessly besides some milliseconds lost here and there
clearTimeout(timer)
}
}, [timerClock]);