How to update nested state properties in React

后端 未结 26 1841
野趣味
野趣味 2020-11-21 06:35

I\'m trying to organize my state by using nested property like this:

this.state = {
   someProperty: {
      flag:true
   }
}

But updating

相关标签:
26条回答
  • 2020-11-21 07:18

    This is clearly not the right or best way to do, however it is cleaner to my view:

    this.state.hugeNestedObject = hugeNestedObject; 
    this.state.anotherHugeNestedObject = anotherHugeNestedObject; 
    
    this.setState({})
    

    However, React itself should iterate thought nested objects and update state and DOM accordingly which is not there yet.

    0 讨论(0)
  • 2020-11-21 07:19

    I take very seriously the concerns already voiced around creating a complete copy of your component state. With that said, I would strongly suggest Immer.

    import produce from 'immer';
    
    <Input
      value={this.state.form.username}
      onChange={e => produce(this.state, s => { s.form.username = e.target.value }) } />
    

    This should work for React.PureComponent (i.e. shallow state comparisons by React) as Immer cleverly uses a proxy object to efficiently copy an arbitrarily deep state tree. Immer is also more typesafe compared to libraries like Immutability Helper, and is ideal for Javascript and Typescript users alike.


    Typescript utility function

    function setStateDeep<S>(comp: React.Component<any, S, any>, fn: (s: 
    Draft<Readonly<S>>) => any) {
      comp.setState(produce(comp.state, s => { fn(s); }))
    }
    
    onChange={e => setStateDeep(this, s => s.form.username = e.target.value)}
    
    0 讨论(0)
  • 2020-11-21 07:21

    In order to setState for a nested object you can follow the below approach as I think setState doesn't handle nested updates.

    var someProperty = {...this.state.someProperty}
    someProperty.flag = true;
    this.setState({someProperty})
    

    The idea is to create a dummy object perform operations on it and then replace the component's state with the updated object

    Now, the spread operator creates only one level nested copy of the object. If your state is highly nested like:

    this.state = {
       someProperty: {
          someOtherProperty: {
              anotherProperty: {
                 flag: true
              }
              ..
          }
          ...
       }
       ...
    }
    

    You could setState using spread operator at each level like

    this.setState(prevState => ({
        ...prevState,
        someProperty: {
            ...prevState.someProperty,
            someOtherProperty: {
                ...prevState.someProperty.someOtherProperty, 
                anotherProperty: {
                   ...prevState.someProperty.someOtherProperty.anotherProperty,
                   flag: false
                }
            }
        }
    }))
    

    However the above syntax get every ugly as the state becomes more and more nested and hence I recommend you to use immutability-helper package to update the state.

    See this answer on how to update state with immutability helper.

    0 讨论(0)
  • 2020-11-21 07:22

    Disclaimer

    Nested State in React is wrong design

    Read this excellent answer.

     

    Reasoning behind this answer:

    React's setState is just a built-in convenience, but you soon realise that it has its limits. Using custom properties and intelligent use of forceUpdate gives you much more. eg:

    class MyClass extends React.Component {
        myState = someObject
        inputValue = 42
    ...
    

    MobX, for example, ditches state completely and uses custom observable properties.
    Use Observables instead of state in React components.

     


    the answer to your misery - see example here

    There is another shorter way to update whatever nested property.

    this.setState(state => {
      state.nested.flag = false
      state.another.deep.prop = true
      return state
    })
    

    On one line

     this.setState(state => (state.nested.flag = false, state))
    

    note: This here is Comma operator ~MDN, see it in action here (Sandbox).

    It is similar to (though this doesn't change state reference)

    this.state.nested.flag = false
    this.forceUpdate()
    

    For the subtle difference in this context between forceUpdate and setState see the linked example.

    Of course this is abusing some core principles, as the state should be read-only, but since you are immediately discarding the old state and replacing it with new state, it is completely ok.

    Warning

    Even though the component containing the state will update and rerender properly (except this gotcha), the props will fail to propagate to children (see Spymaster's comment below). Only use this technique if you know what you are doing.

    For example, you may pass a changed flat prop that is updated and passed easily.

    render(
      //some complex render with your nested state
      <ChildComponent complexNestedProp={this.state.nested} pleaseRerender={Math.random()}/>
    )
    

    Now even though reference for complexNestedProp did not change (shouldComponentUpdate)

    this.props.complexNestedProp === nextProps.complexNestedProp
    

    the component will rerender whenever parent component updates, which is the case after calling this.setState or this.forceUpdate in the parent.

    Effects of mutating the state

    Using nested state and mutating the state directly is dangerous because different objects might hold (intentionally or not) different (older) references to the state and might not necessarily know when to update (for example when using PureComponent or if shouldComponentUpdate is implemented to return false) OR are intended to display old data like in the example below.

    Imagine a timeline that is supposed to render historic data, mutating the data under the hand will result in unexpected behaviour as it will also change previous items.

    Anyway here you can see that Nested PureChildClass it not rerendered due to props failing to propagate.

    0 讨论(0)
  • 2020-11-21 07:25
    const newState = Object.assign({}, this.state);
    newState.property.nestedProperty = "new value";
    this.setState(newState);
    
    0 讨论(0)
  • 2020-11-21 07:25

    Two other options not mentioned yet:

    1. If you have deeply nested state, consider if you can restructure the child objects to sit at the root. This makes the data easier to update.
    2. There are many handy libraries available for handling immutable state listed in the Redux docs. I recommend Immer since it allows you to write code in a mutative manner but handles the necessary cloning behind the scenes. It also freezes the resulting object so you can't accidentally mutate it later.
    0 讨论(0)
提交回复
热议问题