Reputation: 27
I'm creating a simple countdown timer app with React and I am having hard time understanding how setInterval works while React re-renders the component. For example, this following code had timer continue to run even though I had used clearInterval onPause().
let startTimer;
const onStart = () => {
startTimer = setInterval( ()=>{
if ( timeRemaining === 0 ) {
clearInterval(startTimer);
setIsCounting(false)
return
}
updateTimer()
}, 1000)
setIsCounting( (prev) => !prev )
} // end of onStart
const onPause = () => {
setIsCounting( (prev) => !prev )
clearInterval(startTimer)
}
return (
{ props.isCounting ?
<button onClick={props.onPause}> Pause </button>
: <button onClick={props.onStart}> Start </button> }
)
However, the timer successfully pauses when I simply change
let starter;
to
let startTimer = useRef(null)
const onStart = () => {
startTimer.current = setInterval( ()=>{
if ( timeRemaining === 0 ) {
clearInterval(startTimer);
setIsCounting(false)
return
}
updateTimer()
}, 1000)
setIsCounting( (prev) => !prev )
} // end of onStart
const onPause = () => {
setIsCounting( (prev) => !prev )
clearInterval(startTimer.current)
}
What's happening to setInterval when React re-renders its component? Why did my timer continue to run when I didn't use useRef()?
Upvotes: 1
Views: 289
Reputation: 371098
A ref provides what's essentially an instance variable over the lifetime of a component. Without that, all that you have inside an asynchronous React function is references to variables as they were at a certain render. They're not persistent over different renders, unless explicitly done through the call of a state setter or through the assignment to a ref, or something like that.
Doing
let startTimer;
const onStart = () => {
startTimer = setInterval( ()=>{
could only even possibly work if the code that eventually calls clearInterval
is created at the same render that this setInterval
is created.
If you create a variable local to a given render:
let startTimer;
and then call a state setter, causing a re-render:
setIsCounting( (prev) => !prev )
Then, due to the re-render, the whole component's function will run again, resulting in the let startTimer;
line running again - so it'll have a value of undefined
then (and not the value to which it was reassigned on the previous render).
So, you need a ref or state to make sure a value persists through multiple renders. No matter the problem, reassigning a variable declared at the top level of a component is almost never the right choice in React.
Upvotes: 1