react setState array of object using map doesn't work?

Viewed 8174

I have problem doing a setState changing value of a nested array of object. Below code suppose to change question of id 2 to answer: true but it did not, what's wrong?

this.state = {
  questions: [
    {
      id: 1,
      answer: ''
    },
    {
      id: 2,
      answer: ''
    },
  ]
}
//I have a click event somewhere
this.setState(
  {
    questions: this.state.questions.map(q => {
      if (q.id === 2) {
        return {
          ...q,
          answer: true
        }
      } else {
        return { ...q }
      }
    })
  },
  console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)
3 Answers

The console.log(this.state.questions[1]) line is executed before the this.setState line is executed, that's why the old state is printed to the console. You should put the line inside a function to delay the execution:

this.setState(..., () => console.log(this.state.questions[1]));

Also it is recommended to use a function as the first argument if the changed state is derived from the current state because React doesn't apply the new state immediately therefore this.state can be outdated when React applies the new state:

this.setState(state => ({
  questions: state.questions.map(q => {
    if (q.id === 2) {
      return {...q, answer: true};
    }
    return q;
  })
}), () => {
  console.log(this.state.questions[1]);
});

You are not invoking your setState callback. Try like this:

this.setState(
  {
    questions: this.state.questions.map(q => {
      if (q.id === 2) {
        return {
          ...q,
          answer: true
        };
      }
      return { ...q };
    })
  },
  () => console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
);

Though, since you are using the current state to update your state again, it would be better to use functional setState.

this.setState(
  currentState => ({
    questions: currentState.questions.map(q => {
      if (q.id === 2) {
        return {
          ...q,
          answer: true
        };
      }
      return { ...q };
    })
  }),
  () => console.log(this.state.questions[1])
);

Also, you don't have to log your state in a callback to setState. You can log your state in your render method without struggling setState's callback.

this.setState(
  currentState => ({
    questions: currentState.questions.map(q => {
      if (q.id === 2) {
        return {
          ...q,
          answer: true
        };
      }
      return { ...q };
    })
  })
);

....

render() {
    console.log( this.state );
    ....
}

I think it's because Array.map returns an array. Try:

this.setState(
  {
    questions: this.state.questions.map(q => {
      if (q.id === 2) {
        q.answer = true;
      } 
      return q;
    })
  },
  console.log(this.state.questions[1]) // did not see id of 2 being changed to true?
)
Related