2

I am currently building a TicTacToe game and would like to store my current player in state as currentPlayer. After one player moves, I update currentPlayer to the opposite player. However, when I try to log the new state to the console, it's not producing the value of the updated state.

Here is my code:

state = {
    currentPlayer: 'X',
}

// This function is triggered by an onClick attribute.
// It first finds the html element and renders the currentPlayer value from state.
// It then switchs the value of currentPlayer from X to O and calls setState to update the state.
// Why does the console.log not show the updated state value?

userDidMove = () => {
    document.getElementById('cell').innerHTML = this.state.currentPlayer
    let nextPlayer = this.state.currentPlayer === 'X' ? 'O' : 'X'
    this.setState({ 
        currentPlayer: nextPlayer,
    })
    console.log ('userDidMove changed state with ',this.state.currentPlayer)
}

Any help figuring out how to get this function to return the updated state value would be great!

Jacob Crocker
  • 75
  • 1
  • 7

3 Answers3

3

State changes are asynchronous. When your new state is dependent on the previous state, use the state updater function instead.

When the state changes are committed you can use the callback which will have the updated state.

this.setState((previousState) => {
  const nextPlayer = previousState.currentPlayer === 'X' ? 'O' : 'X';

  return {
    currentPlayer: nextPlayer
  }
}, () => {
  // your updated state related code here

  console.log('userDidMove changed state with ', this.state.currentPlayer)
});

this.setState(updatedFunc, callback);
Sushanth --
  • 55,259
  • 9
  • 66
  • 105
  • You are a lifesaver. This worked perfectly. I will have to read up on asynchronicity to better understand what exactly is going on, but man, this really helped. Thank you! – Jacob Crocker Mar 21 '19 at 03:54
1

setState is asynchronous, so the state isn’t updated immediately. You can pass a callback as the second argument to setState that will only be called when state has been updated:

this.setState(
  { currentPlayer: nextPlayer },
  () => console.log(`userDidMove changed state with ${this.state.currentPlayer}`)
);

setState (React Docs):

setState(updater[, callback]) OR setState(stateChange[, callback])

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

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.

NOTE: I suggest observing state using the React Dev Tools, instead of logging it.


UPDATE: This answer initially stated, incorrectly, that setState returned a promise and suggested that you could chain .then() that would be called once state was updated. I've since corrected the answer, with inspiration from @Sushanth's answer.

Mark
  • 694
  • 5
  • 15
JBallin
  • 8,481
  • 4
  • 46
  • 51
  • Logging was just for demonstrative purposes. In my source code, I call a function in it's place that checks for win conditions. I assume your suggestion would work even if I replaced the console.log with a function call? – Jacob Crocker Mar 21 '19 at 03:42
  • Perhaps I am doing something wrong, but I get "Uncaught TypeError: Cannot read property 'then' of undefined" when attempting this. – Jacob Crocker Mar 21 '19 at 03:47
  • Even though `setState` is asynchronous it - counterintuitively - does not return a promise, so `.then` will not work. You have to give it a second argument of a function to execute after it finishes. `this.setState({}, () => console.log(this.state));` – Mark Mar 21 '19 at 03:51
  • @Mark this is pretty much exactly what Sushanth said. So, points to you too! Thanks! – Jacob Crocker Mar 21 '19 at 03:56
  • Sorry to lead you down the wrong path here. I've corrected my answer, for posterity. – JBallin Mar 21 '19 at 04:21
0

State changes are asynchronous. so use a function instead and the second parameter of setState function you may call the callback function to console or something else to do.

this.setState(() => ({currentPlayer: nextPlayer}), () => {
  console.log('state', this.state.currentPlayer);
})
Ikram Ud Daula
  • 1,213
  • 14
  • 21