How to update nested state properties in React

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.


To write it in one line

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });

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 and sandbox.

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 sandbox

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.

state-flow state-flow-nested

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


Sometimes direct answers are not the best ones :)

Short version:

this code

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

should be simplified as something like

this.state = {
    somePropertyFlag: true
}

Long version:

Currently you shouldn't want to work with nested state in React. Because React is not oriented to work with nested states and all solutions proposed here look as hacks. They don't use the framework but fight with it. They suggest to write not so clear code for doubtful purpose of grouping some properties. So they are very interesting as an answer to the challenge but practically useless.

Lets imagine the following state:

{
    parent: {
        child1: 'value 1',
        child2: 'value 2',
        ...
        child100: 'value 100'
    }
}

What will happen if you change just a value of child1? React will not re-render the view because it uses shallow comparison and it will find that parent property didn't change. BTW mutating the state object directly is considered to be a bad practice in general.

So you need to re-create the whole parent object. But in this case we will meet another problem. React will think that all children have changed their values and will re-render all of them. Of course it is not good for performance.

It is still possible to solve that problem by writing some complicated logic in shouldComponentUpdate() but I would prefer to stop here and use simple solution from the short version.