简体   繁体   中英

ReactJS: Update nested state object properties without replacing whole object using setState()

I am trying to apply best practices when changing multiple property values of a nested object in my component's state.

My component code currently looks like this:

class App extends Component {
  state = {
    object: {
      a: 10,
      b: 7,
      c: 12,
    }
  }
  render() {
    return (
      <div>
        <button onClick={this.setState({ object: { a: 5 }})}>change value a </button>
        <button onClick={this.setState({ object: { b: 2 }})}>change value b </button>
        <button onClick={this.setState({ object: { c: 3 }})}>change value c </button>
      </div>
      <p>{this.state.object.a}</p>
      <p>{this.state.object.b}</p>
      <p>{this.state.object.c}</p>
    );
  }
}

Before any button is clicked, you see three buttons followed by paragraphs reading 10 , 7 and 12 on the page.

Once I click the first button labeled "change value a", value b and c are destroyed in my components state object which causes only the value of 5 to show. If I click the second button, then only 2 will show and prop a and c are gone.

I understand the behaviour, however I would like to know the best way to solve it. I want to keep it so all the values showing, and be able to update them in any order while the other values remain.

I am also pretty sure I am mutating the state directly and I know this is bad practice. I just don't know the right practice in this situation.

First to address what you said, "I am mutating the state directly" .

You are are not mutating state directly since you are calling this.setState() .

Since you want to update a specific part of object: {} , you can use the spread syntax or Object.assign() as follows:

this.setState({ object: { ...this.state.object, a: 5 } })

or

this.setState({ object: Object.assign({}, this.state.object, { a: 5 }) })

Because you called this.setState() in render , you will get the Maximum call stack exceeded error .

I can think of four ways to solve this, I'll show two of those.

  1. Extract your call into a class method, then pass the reference to the click event handler.
  2. Change this onClick={this.setState({ object: { c: 3 }})} to onClick={() => this.setState({ object: { c: 3 }})}

One way to do this would be by using the spread ... operator on the nested object to merge the update of say { a: 5 } , with the prior state of the object field:

// Create a new state object, with updated value of "5" for nested field "object.a"
{ object: { ...state.object, a: 5 }}

There are a few ways to incorporate this with setState() - one simple way is via a callback passed to setState() :

this.setState(state => { object: { ...state.object, a: 5 }})

This allow you to merge the nested object s prior state, with the state changes such as { a : 5 } on object , without completely replacing all the nested object value in your state.

In the case of your render function, you could update the rendered result like so:

 render() {
  return (
  <div>
    <button onClick={ () => this.setState(state => { object: { ...state.object, a: 5 }})}>change value a </button>
    <button onClick={ () => this.setState(state => { object: { ...state.object, b: 2 }})}>change value b </button>
    <button onClick={ () => this.setState(state => { object: { ...state.object, c: 5 }})}>change value c </button>
  </div>
  <p>{this.state.object.a}</p>
  <p>{this.state.object.b}</p>
  <p>{this.state.object.c}</p>
  );
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM