Do yourself a favor — let this never be a problem again
They both take two arguments — a function to do and a length of time (in milliseconds).
The first, setTimeout, will perform the function once after the time you specified has passed.
If you want to prevent the execution of the function, calling clearTimeout( ) and passing it the variable assigned to the setTimeout function will cut it off if the function hasn’t already been run.
The second function, setInterval, performs the function after the time you specified has passed until you tell it to stop.
To stop the cycle, you must call clearInterval( ), passing in the variable you assigned to the setInterval function.
In a normal vanilla JS program, you should be able to use these functions without much difficulty once you understand how they work….
But in React, things can get tricky rather quickly. Here’s a simple timer component in React:
The counter is set to 10 when the component is mounted. Once it’s rendered and after one second, setTimeout runs the callback function that first checks if the counter is greater than zero and, if it is, decreases the counter by one using setState. Then it returns a div with the time remaining in the counter. This works as a timer because setState causes the component to re-render, which causes setTimeout to be run again, until the counter reaches zero.
This approach works, but there’s a glaring problem — if there is any functionality in the timer’s parent component that causes the timer to re-render…well let’s just say that problems quickly arise.
In this snippet, if a user clicks the button on line 28, it will run the function on line 12. That function uses setState, which would cause Game’s render block to re-run, which in turn would cause the Timer’s render block to re-run. The result will be a timer that slows down when you click the inflate button, as the timer is reset every time you do so.
But that’s just the beginning of your timer woes. Let’s say you want something to actually happen when your counter hits zero — we’d need to add some action in the else block in our Timer component. We’ll pass it a simple function from the Game component called finishGame, which will setState to adjust how the child components are rendered.
Makes sense, right? What could possibly go wrong?
If you guessed “an infinite loop that makes your computer’s fan go nuts”, you’d be correct. The counter is stored in state, and it was never updated to not equal zero. Timer gets rendered, which runs finishGame because the counter is at zero, which re-renders it, which runs finishGame, which re-renders it…..until you call uncle and ctrl-C.
Here you can see that I’ve rendered the Timer and the Game components separately. On the Timer side…
After finishing the game, I reset the counter. I’ve also added some simple logic to determine whether or not to even start the timer again.
There’s likely a better solution than this, and I’m sure I’ll learn about it as soon as I’ve published this. Nonetheless, the core takeaways remain:
- Simplify your timer — have it run a counter, then send a message back when it hits zero
- Separate your timer as it’s own concern, inside it’s own component
- Render your timer as little as possible
- Have tight control over the situations where it is rendered