4

I am writing a version of Conway's Game of Life in React. The component's state contains the grid describing which of the cells is alive at the current time. In each game loop, the new grid is calculated and the state is updated with the next iteration.

It occurs to me that since setState is asynchronous, when repeatedly calling the iterate function with setInterval, I am not guaranteed to be using the current version of grid each time iterate runs.

Is there an alternative to using setInterval in React that would avoid any potential issues caused by setState being asynchronous?

Here are the relevant functions that describe the game loop:

  go = () => {
    const { tickInterval } = this.state;
    this.timerId = setInterval(this.iterate, 570 - tickInterval);
    this.setState({
      running: true,
    });
  };

  iterate = () => {
    const { grid, gridSize, ticks } = this.state;
    const nextGrid = getNextIteration(grid, gridSize);
    this.setState({
      grid: nextGrid,
      ticks: ticks + 1,
    });
  };
wbruntra
  • 1,021
  • 1
  • 10
  • 18
  • 1
    `setState` can take a [callback function](https://stackoverflow.com/q/42038590/1813169) which executes *after* the new state has completely propagated. – MTCoster Nov 19 '18 at 14:06
  • With `this.timerId` coming from `setInterval`, I can easily stop the loop by calling `clearInterval`. Using callbacks, I guess the callback function would be using `setTimeout`? What would be the way to stop a loop consisting of callback functions? – wbruntra Nov 19 '18 at 14:15

3 Answers3

1

If you need to set state based on a current state, it is wrong to directly rely on this.state, because it may be updated asynchronously. What you need to do is to pass a function to setState instead of an object:

this.setState((state, props) => ({
  // updated state
}));

And in your case it would be something like:

iterate = () => {

  this.setState(state => {
    const { grid, gridSize, ticks } = state;
    const nextGrid = getNextIteration(grid, gridSize);
    return {
      grid: nextGrid,
      ticks: ticks + 1
    }
  });

};
n1stre
  • 5,856
  • 4
  • 20
  • 41
0

SetState is Asynchronous

this.setState({
    running: true,
});

To make it synchronously execute a method:

this.setState({
     value: true
}, function() {
    this.functionCall() 
})
J Dorrian
  • 206
  • 3
  • 15
0

If you have a look at the react official documentation, the setState api does take a callback in following format:

setState(updater[, callback])

Here the first argument will be your modified state object and second argument would be callback function to be executed when setState has completed execution.

As per the official docs:

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

You can have a look at official docs to get more information on this.

Pranay Tripathi
  • 1,614
  • 1
  • 16
  • 24
  • If I understood the docs correctly, I don't actually need to use the callback, the first argument supplied to the `updater` function will use the most up-to-date version of `state`, so I think the answer from streletss is easier to implement. – wbruntra Nov 24 '18 at 14:54