JavaScript React Components Managing State and Data Flow Update State Based on a Player's Index

How does the setState method know what player's score to change?

The object you pass in to return from the call back function only has a "score" property. You don't specify which player's is getting updated only what the new score should be. How does this work? It's working for me but I have no idea how

here is my code:

this.setState(prevState => ({ score: prevState.players[index].score += change }));

Again I have no idea how setState knows which player to update with the new score. Shouldn't the object returned by the callback look something like this (which doesn't work)?

{
    players: {
        prevState.players[index]: {
            score: prevState.players[index].score += change
        }
    }
}
Josh Olson
Josh Olson
13,062 Points

Look inside App.js, it contains players which is an array. You're calling it here using players[index]. That's all there is to it: an array with an index value.

Again I have no idea how setState knows which player to update with the new score. Shouldn't the object returned by the callback look something like this (which doesn't work)?

Kinda. It's not going to create that long nested object, but instead what's at the very end. In this case, score. It's going to return the integer because it's evaluating everything.

Open up your developer console with F12 and copy paste this code. Here's an oversimplified version.

   let players = [
         {
           name: "Guil",
           score: 10,
           id: 1
         },
         {
           name: "Treasure",
           score: 50,
           id: 2
         },
         {
           name: "Ashley",
           score: 30,
           id: 3
         },
         {
           name: "James",
           score: 65,
           id: 4
         }
   ];


   let index = 2;
   console.log('players', players);

   console.log('players[index]', players[index]);

   console.log('players[index].score', players[index].score);

In the first log, it returns players, which is the entire array.
Then it returns index 2 of players, which is the object for Ashley.
Finally it returns Ashley's score, which is just an integer.

Making sense now?

10 Answers

Alberto Di Martino
Alberto Di Martino
11,796 Points

@Joshua O'Brian has the correct idea +1 !

Bradley Coughlin
Bradley Coughlin
2,214 Points

I agree, it seems that Joshua O'Brian 's code is the better approach. It is returning the updated player object with the modified 'score' and replacing it in the current state. This eliminates the 'score' at the top of the current state object that you'd see if you use the code provided in the video.

For me, the code shown by Joshua O'Brian makes the most sense! Can we agree that the version shown in the video is some kind of queer version of using setState() and we should not try anymore to understand how the hell it's happening to make its job?...

Alberto Di Martino
Alberto Di Martino
11,796 Points

It is still unclear for me, how the function know which player to update? does

prevState.players[index].score += change 

returns a new copy of the whole state?

Alberto Di Martino
Alberto Di Martino
11,796 Points

Ok got it, actually, this video is doing something that is not recommended on the official React JS Website. From the docs:

state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.

In our case "state" is "prevState"

We are mutating it on this.setState which is discouraged. We have to create a new object, in this case, a new players arrays, update the copy and then assign the state's player array to this new copy.

I did it with the following code. I'm learning JS too so maybe there is a more elegant/efficient way to do it.

  handleScoreChange = (index, delta) => {
    this.setState(prevState => {
      const playersCopy = prevState.players.map(u => {
        const copiedPlayer = Object.assign({}, u);
        copiedPlayer.approved = true;
        return copiedPlayer;
      });
      playersCopy[index].score += delta;
      return { players: playersCopy };
    });
  };

I'm not quite sure if you understood my question.

I understand how "prevState.players[index].score" equals the previous score for the player, adding the change to it gives us our new score. But how does setState know what "score" we are trying to set?

Look at my code again:

this.setState(prevState => ({ score: prevState.players[index].score += change }));

We are setting "score" equal to "prevState.players[index].score += change". I want to know how setState knows what player "score" is referring to.

Should it not look something like this?:

this.setState(prevState => ({players[index].score: prevState.players[index].score += change}));
Joshua O'Brian
Joshua O'Brian
18,281 Points

I feel like there is some mutation going on here. If you were to use react dev tools you might see that there is now a variable in state called score with that value aslo. if anything it should look like this

   this.setState(prevState => ({
     //this will return a new copy of the players array with value
      players: prevState.players.map((player, i) => {
           if(i === index) player.score += change;
           return player;
     })
  }));
Nathan Yebgui
Nathan Yebgui
11,196 Points

this simple solution is working fine for me.

 this.setState( prevState => (prevState.players[index].score += delta));

You're still mutating prevState directly, not returning a new object.

Leonardo Guzman
PRO
Leonardo Guzman
Pro Student 11,604 Points

ok, here is what's actually happening.

I've rewrote code a little bit, so it's more clear.

First they mutate the previous state by increasing the player's score. Then they add some random variable to state and name it 'score' what is very confusing. So eventually what you get is mutated players array + useless 'score'

    handleScoreChange = (index, delta) => {
        this.setState(prevState => {
            prevState.players[index].score += delta;
            return {
                score: 1
            }
        });
    };

Hope it helps.

Showing the output of the state to the log after update shows score added as a new state item in addition to the players. This isn't what is intended and I believe the code in the video is wrong.

The update to the score is happening in the line prior to the return in the code below. My initial implementation returned an updated players array. It seems that is not necessary since we have a reference in prevState to the actual player whose score needs to be updated, and this can be done via prevState.players[index].score += delta;

Perhaps a future video clarifies this issue but my conclusion is that returning an empty object is what is needed as shown below, and in my testing this works fine.

The corrected code that I have tested follows, please note return {}

  this.setState( prevState => {
      prevState.players[index].score += delta;
      return { }
    });
  }

This was driving me nuts. I ended up doing this. It updates the players array without adding the extra score property to state (the one that appears outside of the players array in React DevTools.

handleChangeScore = (delta, index) => {
  let players = [...this.state.players]  //spread the players array from state into a new players array
  players[index].score += delta;       //update the score of the player at the index
  this.setState({players: players});   //update state the players property in state with new players array with the updated scores
}

I still don't know how the original code in the video managed to update the correct player's score AND create the extra score property in state. Any feedback on what I have here?

If I'm understanding correctly, what Leonardo Guzman and rlogwood are saying is that adding the extra object into setState is what helps to force state to update? Like, it could be literally anything? Still messing with this. Maybe I'll have a better handle on it in a minute.

This also seems to work just fine. Much closer to what's in the video, but simpler, and it doesn't produce the extra useless property in state:

handleChangeScore = (delta, index) => {
  this.setState( prevState => {
    return prevState.players[index].score += delta
  });
}

Hi Daniel,

Seeing your post motivated me to make sure I understand this also. Here's the result of my investigation:

gist react_state_fn_upd_explore.js

In short the setState method returns either an object that has state updates or a function that returns an object with state updates. The returned object represents updates you want to make to the components state. The updates specified in the object are merged into the component's state. So in the case where I returned {}, it works because there are no new state values being set. Instead using prevState to update the score changes the actual score in the state for the component because prevState is a reference to it.

Using the function form of the setState protects us from the batching of state updates that React can do and those asynchronous state updates mean you need a function that receives its current value (prevState) before using values from it to set the next state values.

That said, even though returning {} works in this case, maybe the clearest version of the update for a maintainer would be

(prevState) => {prevState.players[index].score += delta; return { players : prevState.players }}; 

Steven Parker, Guil Hernandez any insight in why the code of the video works? All around the Internet it says setState doesn't handle nested states, but here this

this.setState(prevState => ({ score: prevState.players[index].score += change }));

is working, and it also creates a new score property in the state object. How does setState know which player to modify? Perhaps it's something related to the event handlers and where the changeScore function is called from?

I think I figured it out, if anyone has the same doubt: setState doesn't know which player object to change. It actually creates a new score property in the root of the state object. The reason the score is still updated is because this line

prevState.players[index].score += change

is mutating the state directly, due to the addition assignment operator. If the operator was a simple addition, the original prevState.players[index].score wouldn't be changed.