j11d
j11d

Reputation: 13

How is React updating his state?

I have a question concerning React and how state must be updated. Let's say we have a class Players containing in its state an array of objects called players. We want to update one player in this array. I would have done it this way:

class Players extends Component {

  state = {
      players: []
  }

  updatePlayer = id => {
      const players = this.state.players.map(player => {
        player.updated = player.id === id ? true:false;
        return player
      });
      this.setState({players: players});
  }
}

But my coworker just did it this way, and it's also working:

updatePlayer = id => {
    const playerObj = this.state.players.find(item => {
      return item.id === id
    })

    if (playerObj) {
      playerObj.updated = true

      this.setState({ playerObj })
    }
}

React's function setState update the players array without telling explicitly to do it. So, I have two questions:

Thank you all for your explanations !

Upvotes: 1

Views: 91

Answers (4)

Estus Flask
Estus Flask

Reputation: 222503

The difference is that second snippet misuses setState to trigger an update because it uses playerObj dummy property. This could be achieved with forceUpdate.

Neither of these ways are correct. Immutable state is promoted in React as a convention. Mutating existing state may result in incorrect behaviour in components that expect a state to be immutable. They mutate existing player object, and new player.update value will be used everywhere where this object is used, even if this is undesirable.

An idiomatic way to do this is to use immutable objects in state:

updatePlayer = id => {
  this.setState(({ players }) => ({
    players: players.map(player => ({
      ...player,
      updated: player.id === id
    }));
  });
}

Notice that setState is asynchronous, updater function has to be used to avoid possible race conditions.

Upvotes: 2

Prince Hernandez
Prince Hernandez

Reputation: 3721

well, basically they are not the same, your coworkers code is working just because he is using an old reference of the object. so, lets take a look:

  updatePlayer = id => {
      const players = this.state.players.map(player => {
        player.updated = player.id === id ? true:false;
        return player
      });
      this.setState({players: players});
  }

on your function, you are creating a new array using your old array, which is the correct way of doing this.

updatePlayer = id => {
    const playerObj = this.state.players.find(item => {
      return item.id === id
    })

    if (playerObj) {
      playerObj.updated = true

      this.setState({ playerObj })
    }
}

here your friend is editing the reference of the object that he got using find and then saving a playerObj which is nothing more than the reference of a player from the array that you wanted to edit. after this you should notice that the new state will be something like

this.state = {
    players: [p1, p2 ,p3, p4], // each p is a player.
    //notice that playerObj is now a reference to some of the P on the players array
    playerObj: {
        ...playerstuff,
        updated: true
    }
}

hope it helps :)

Upvotes: 0

Vladyslav Tereshyn
Vladyslav Tereshyn

Reputation: 235

Both of them are not correct, because you are mutating state. The best way is a create a deep copy of this array ( just clone ) and after that make some changes with this cloned array

You can also use lodash _.cloneDeep();

For example

class Example extends React.Component {
  state = {
    players: [
      {id: 0, name: 'John'};
    ]
  };
  
  updatePlayer = id => {
    const { players } = this.state;
    const clonePlayers = players.map(a => ({...a}));
    
    const player = clonePlayers.find(playerId => playerId === id);
    
    player.name = 'Jack';
    
    this.setState(() => ({
      players: clonePlayers
    }));
  }
  
  render() {
    return (
      <div>some</div>
    );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Upvotes: 0

Keith Rousseau
Keith Rousseau

Reputation: 4475

Yes, all it's using a reference. All javascript objects are references so whenever you do a find you get a reference to the object, so mutating it will update it.

const players = this.state.players.map(player => {
        return { ...player, updated: player.id === id };
});
this.setState({players: players});

As for the recommended way, you should stick with yours where you explicitly update the state variable that you care about.

Upvotes: 0

Related Questions