Andrew Kim Andrew Kim - 29 days ago 13
Javascript Question

setInterval with setState in React

I have a timer using

setInterval()
in a React component and I'm unsure what the best practices are in order to start and stop this interval in respect to using
state
. I'm running into some asynchronous issues with that.

Let's say I have a set of links in my React component that render and execute the callback fine:

let links = [10, 50, 100, 500, 1000].map((num) => {
return(
<Link key={num} onClick={(e) => this.switchNums(num)} to={`/somePath/${num}`}>{num}</Link>
)
})


Here's the
switchNums()
function, where i want it to reset an existing timer:

switchNums(num){
this.stopTimer()
this.reset(num)
}


Here's
startTimer()
,
stopTimer()
and
reset()
:

startTimer(){
if(!this.state.timerId){
let timerId = setInterval(()=>{
let timer = this.state.timer + 1
this.setState({
timer: timer,
timerId: timerId
})
}, 1000)
}
}

stopTimer(){
clearInterval(this.state.timerId)
this.setState({timerId:null})
}

reset(size){
this.setState({
gameOver: false,
counter: 0,
correct: 0,
numbers: this.getRandomNumbers(size),
timer: 0
}, this.startTimer())
}


One of the bugs is clicking on the links rapidly will cause multiple intervals to fire despite the
if
condition in
startTimer()
. I'm guessing this has to do with the asynchronous nature of
setState()
. Another bug (and I think related) is that when i click slowly, it only starts the interval every other time.

Can anyone shed some light on this? Or what they've done to circumvent asynchronous issues with
setState
being used in conjunction with
setInterval
(any way set state can return a promise?), Or which lifecycle methods would be best for this type of situation?

Answer

I think the biggest flaw here is that you're using state to store your interval. While technically possible, I see no reason why you would actually want to do that.

Instead, just use a local variable to your component:

startTimer(){
  if(!this.timerId){     
    this.timerId = setInterval(()=>{
      //your function
    }, 1000);
  }
}

stopTimer(){
  clearInterval(this.timerId);
}

So I don't think you need to use the state at all here for your timer. You have some other general questions in your post though that are related to state, and I'll try to answer those below. Just bear in mind that they are irrelevant in solving your particular issue.


What have they've done to circumvent asynchronous issues with setState()?

You can use a callback to execute code after the state has been set. There's a section of the official docs about this; here's what it says:

The second parameter is an optional callback function that will be executed once setState is completed and the component is re-rendered.

setState(nextState, callback);

Which lifecycle methods would be best for this type of situation?

The same section of the doc as above continues:

Generally we recommend using componentDidUpdate() for such logic instead.

If you have multiple setState in your function, and you want to execute specific code after a specific event, I think you're fine using the callback. For more general purposes use the life-cycle method above.