620

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

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

But updating state like this,

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

doesn't work. How can this be done correctly?

serraosays
  • 7,163
  • 3
  • 35
  • 60
Alex Yong
  • 7,425
  • 8
  • 24
  • 41
  • Try to read: http://stackoverflow.com/questions/18933985/this-setstate-isnt-merging-states-as-i-would-expect – Andrew Paramoshkin Mar 27 '17 at 07:58
  • 166
    Simply not using nested state is an unacceptable answer for how widely used React is today. This situation is going to come up and developers need an answer to this. – serraosays Apr 22 '19 at 03:50

37 Answers37

673

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.

Théophile
  • 1,032
  • 10
  • 16
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Does this not access the state directly and therefore break the idea of 'states' in general? – Ohgodwhy Apr 15 '17 at 09:14
  • 4
    @Ohgodwhy , no this is not accessing the state directly since {...this.state.someProperty} retuns a new obejct to `someProperty` and we are modifying that – Shubham Khatri Apr 15 '17 at 09:17
  • Is the `...` making a real cop? Or is it just passing a reference to the `object`? The latter would violate react rules. The state is `immuatable` – four-eyes Nov 22 '17 at 16:16
  • @Stophface, `...` or `spread operator` makes a one level deep cloning of the object. If your state is highly nested, you could either make level by level copy of the state or use `immutability-helper` package – Shubham Khatri Nov 22 '17 at 17:57
  • Thanks. You also could use `_.cloneDeep()` from `lodash`. – four-eyes Nov 22 '17 at 19:57
  • 6
    @Stophface We can use Lodash to deep clone sure, but not everyone would include this library just to setState – Shubham Khatri Nov 23 '17 at 05:36
  • 35
    Doesn't this violate https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous ? If two changes to the nested object got batched the last change would overwrite the first. So the most correct way would be: `this.setState((prevState) => ({nested: {...prevState.nested, propertyToSet: newValue}})` A bit tedious though... – olejorgenb Dec 21 '17 at 20:55
  • @olejorgenb I have mentioned this approach too, also creating a cloned object and updating its value to setState won't perform batched updates – Shubham Khatri Dec 22 '17 at 04:15
  • 3
    I don't think you need the `...prevState,` in the last example, you only `someProperty` and its children – Jemar Jones May 25 '18 at 19:43
  • I have added [the answer](https://stackoverflow.com/a/58538673/2093151) for useState hook case for completeness. – Andrew Oct 24 '19 at 10:00
  • For anyone working with crazy nested states, I recommend checking out this answer: https://stackoverflow.com/a/35629422/11993942 – radihuq Jan 15 '20 at 04:50
  • "Now, the spread operator creates only one level nested copy of the object" Made a quick test on chrome console, and spread operator seems to go really further. – Anthony Bobenrieth Apr 04 '20 at 00:14
  • 1
    @AnthonyBobenrieth Check the MDN docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax – Shubham Khatri Apr 04 '20 at 04:00
  • What if I do like this : `let myObjCopy = this.state.myObj; myObjCopy.info.name.first = 'shrek' ` ... does this impact the `state.myObj` as well ?? since `myObjCopy` was not deep copied from state. – kernelman Oct 03 '20 at 09:05
  • var someProperty = {...this.state.someProperty} someProperty.flag = true; this.setState({someProperty}) Pieces of Art. Thank you – sg28 Jun 10 '21 at 22:49
181

To write it in one line

this.setState({ someProperty: { ...this.state.someProperty, flag: false} });
Yoseph
  • 2,356
  • 1
  • 13
  • 18
  • 21
    It is recommended to use the updater for setState instead of directly accessing this.state within setState. https://reactjs.org/docs/react-component.html#setstate – Raghu Teja May 23 '18 at 12:52
  • 10
    I believe that's saying, don't read `this.state` immediately after `setState` and expect it to have the new value. Versus calling `this.state` within `setState`. Or is there also an issue with the latter that isn't clear to me? – trpt4him Jun 01 '18 at 13:06
  • 4
    As [trpt4him](https://stackoverflow.com/users/877175/trpt4him) said, the link you gave talks about the problem of accessing `this.state` after `setState`. Besides, we are not passing `this.state` to `setState`. The `…` operator spread properties. It is the same as Object.assign(). https://github.com/tc39/proposal-object-rest-spread – Yoseph Jun 04 '18 at 04:27
  • 5
    @RaghuTeja maybe just making `this.setState(currentState => { someProperty: { ...currentState.someProperty, flag: false} });` could allow to avoid this issue? – Webwoman Jun 12 '19 at 22:09
  • I have added [the answer](https://stackoverflow.com/a/58538673/2093151) how to achieve the same with useState hook for completeness. – Andrew Oct 24 '19 at 09:58
153

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.

Konstantin Smolyanin
  • 17,579
  • 12
  • 56
  • 56
  • 14
    I think there are use cases where a nested state is perfectly fine. E.g. a state dynamically keeping track of key-value pairs. See here how you can update the state for just a single entry in the state: https://reactjs.org/docs/optimizing-performance.html#the-power-of-not-mutating-data – pors Aug 30 '18 at 13:45
  • 8
    I want to create an altar in your honor and pray to it every morning. It took me three days to reach this answer that perfectly explains the OBVIOUS design decision. Everyone just tries to use the spread operator or do other hacks just because nested state looks more human-readable. – Edeph Nov 29 '18 at 14:06
  • 6
    How do you suggest then, to use React to do things like rendering an interactive tree (for example, my company has a bunch of sensors in different parts of the world, each of different type, with different properties, in different locations (80+), and OF COURSE they are organized in a hierarchy as each deployment costs thousands of dollars and is a complete project so it would be impossible to manage them without a hierarchy). I'm pretty curious on how you wouldn't model things like trees as... trees, in React. – DanyAlejandro Feb 04 '19 at 23:44
  • 2
    @DanyAlejandro ok, maybe you are one of that 0.1% of people who need tree in the React state. I didn't say that it is strictly prohibited to do such a thing, I just said that your application may become slow and/or complicated because of that. And if you can use a flat state you should do that. – Konstantin Smolyanin Feb 05 '19 at 20:02
  • 34
    seems like React was not made for representing anything that is stored as a recursive structure of non-trivial depth. It's a bit disappointing as tree-views appear in almost every complex application out there (gmail, file managers, any document management system,..). I've used React for these things, but always had a React zealot telling everyone you're doing anti-patterns and suggesting the solution is keeping deep copies of the tree in the state of every node (yes, 80 copies). I'd love to see a non-hacky tree-view component that doesn't break any React rules. – DanyAlejandro Feb 05 '19 at 21:31
  • 4
    This, a million times. I have spent so much time messing with nested parameters because of unhelpful comments online showing that it is possible in some confusing manner. The react docs should really point this out at the beginning of the section on setState! – forgetso Apr 09 '20 at 10:18
  • @KonstantinSmolyanin you said that when re-creating whole parent object React will rerender all child components, but React shouldn't rerender those children whose "key" attribute didn't change. Am I right? – Broccoli Nov 02 '20 at 10:21
  • @Broccoli we are talking about plain JS object in the component's state, not about a tree of React components – Konstantin Smolyanin Nov 03 '20 at 13:55
  • 1
    Oh please everyone upvote this answer! The solution to my problem was so much simpler with no need for nested props and this answer made my realize it! – Anestis Kivranoglou Aug 03 '23 at 08:36
112

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.

Qwerty
  • 29,062
  • 22
  • 108
  • 136
  • 14
    You shouldn't be mutating any react state anywhere. If there are nested components that use data from state.nested or state.another, they won't re-render nor will their children. This is because the object did not change, thus React thinks nothing has changed. – Zeragamba May 12 '18 at 16:51
  • @SpyMaster356 I have updated my answer to address this. Also note that MobX, for example, ditches `state` completely and uses custom _observable_ properties. 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. [Use Observables instead of state in React components.](https://alexhisen.gitbooks.io/mobx-recipes/content/use-observables-instead-of-state-in-react-components.html) – Qwerty Aug 09 '18 at 09:44
  • Also I added an example http://react-experiments.herokuapp.com/state-flow (link to git at the top) – Qwerty Aug 09 '18 at 16:28
  • 1
    What if you load for example an object with nested attributes from a database into state? How to avoid nested state in that case? – tobiv Jan 07 '19 at 00:17
  • 5
    @tobiv It's better to transform the data upon a fetch into a different structure, but it depends on the usecase. It's okay to have a nested object or array of objects in a state if you think of them as compact units of _some_ data. The problem arises when you need to store UI switches or other observable data (i.e. data that should immediately change the view) as they must be flat in order to correctly trigger internal React diffing algorithms. For changes in other nested objects, it's easy to trigger `this.forceUpdate()` or implement specific `shouldComponentUpdate`. – Qwerty Jan 07 '19 at 17:44
  • 2
    A little bit offtop - could you provide some info why (expression1, expression2) in `this.setState(state => (state.nested.flag = false, state))` works that way? (docs, mdn, search request etc) – Mega Proger May 19 '20 at 20:47
  • 1
    @MegaProger This is a [Comma operator ~MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator). And in fact, returning just an empty object `(exp1, {})` in the `setState` call would also work, but I didn't want to create new objects unnecessarily. [sandbox](https://codesandbox.io/s/update-nested-state-tkpil?file=/src/App.js) – Qwerty May 20 '20 at 14:33
  • 2
    So, it might work better to have an entire structure outside of state, but use state to link to the specific area of the structure that you need to access at a particular time. – kojow7 Dec 18 '20 at 04:33
25
const newState = Object.assign({}, this.state);
newState.property.nestedProperty = "new value";
this.setState(newState);
lami
  • 1,410
  • 12
  • 16
  • 2
    seems the more elegant solution here, 1) avoiding the tedious spreading inside setState and 2) put nicely the new state inside setState, avoiding to mutate the directly inside setState. Last but not least 3) avoiding the use of any library for just a simple task – Webwoman Jan 31 '19 at 18:50
  • The two examples are not equivalent. If the `property` contains several nested properties, the first will delete them, the second won't. https://codesandbox.io/s/nameless-pine-42thu – Qwerty Oct 08 '19 at 09:06
  • Qwerty, you are right, thanks for the effort. I removed the ES6 version. – lami Oct 16 '19 at 13:54
  • I used your solution. My state obj is small, and it worked fine. Will React render for the changed state only or will it render everything? – Kenji Noguchi Apr 13 '20 at 06:03
  • I believe the very heart of react is to make smart diffs and only update the right parts of the DOM. https://reactjs.org/docs/reconciliation.html – lami Apr 14 '20 at 13:44
  • `Object.assign` only does a shallow copy, so it would mutate the object under the `property` property. Don't mutate the previous state. – Emile Bergeron Aug 30 '21 at 03:33
23

If you are using ES2015 you have access to the Object.assign. You can use it as follows to update a nested object.

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

You merge the updated properties with the existing and use the returned object to update the state.

Edit: Added an empty object as target to the assign function to make sure the state isn't mutated directly as carkod pointed out.

Alyssa Roose
  • 239
  • 2
  • 6
22

We use Immer to handle these kinds of issues.

Just replaced this code in one of our components

this.setState(prevState => ({
   ...prevState,
        preferences: {
            ...prevState.preferences,
            [key]: newValue
        }
}));

With this

import produce from 'immer';

this.setState(produce(draft => {
    draft.preferences[key] = newValue;
}));
    

With immer you handle your state as a "normal object". The magic happens behind the scene with proxy objects.

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
17

There are many libraries to help with this. For example, using immutability-helper:

import update from 'immutability-helper';

const newState = update(this.state, {
  someProperty: {flag: {$set: false}},
};
this.setState(newState);

Using lodash/fp set:

import {set} from 'lodash/fp';

const newState = set(["someProperty", "flag"], false, this.state);

Using lodash/fp merge:

import {merge} from 'lodash/fp';

const newState = merge(this.state, {
  someProperty: {flag: false},
});
tokland
  • 66,169
  • 13
  • 144
  • 170
  • Good answer, maybe you wish to add that you have to call `this.setState(newState)` for the lodash/fp methods as well. Another gotcha is that lodash `.set` (non-fp) has a different order of arguments which tripped me up for a while. – Toivo Säwén Jan 24 '20 at 15:45
  • Merge has some strange behavior, when calling `this.setState(newState)`, it creates another state item called `newState` in `this.state`. Any idea why? – Barry Chapman Sep 01 '20 at 17:08
11

Although you asked about a state of class-based React component, the same problem exists with useState hook. Even worse: useState hook does not accept partial updates. So this question became very relevant when useState hook was introduced.

I have decided to post the following answer to make sure the question covers more modern scenarios where the useState hook is used:

If you have:

const [state, setState] = useState({
  someProperty: {
    flag: true,
    otherNestedProp: 1
  },
  otherProp: 2
})

you can set the nested property by cloning the current and patching the required segments of the data, for example:

setState(current => { ...current,
  someProperty: { ...current.someProperty,
    flag: false
  }
});

Or you can use Immer library to simplify the cloning and patching of the object.

Or you can use Hookstate library (disclaimer: I am an author) to simply the management of complex (local and global) state data entirely and improve the performance (read: not to worry about rendering optimization):

import { useHookstate } from '@hookstate/core'

const state = useHookstate({
  someProperty: {
    flag: true,
    otherNestedProp: 1
  },
  otherProp: 2
})

get the field to render:

state.someProperty.flag.get()
// or 
state.get().someProperty.flag

set the nested field:

state.someProperty.flag.set(false)

Here is the Hookstate example, where the state is deeply / recursively nested in tree-like data structure.

Andrew
  • 2,055
  • 2
  • 20
  • 27
7

Here's a variation on the first answer given in this thread which doesn't require any extra packages, libraries or special functions.

state = {
  someProperty: {
    flag: 'string'
  }
}

handleChange = (value) => {
  const newState = {...this.state.someProperty, flag: value}
  this.setState({ someProperty: newState })
}

In order to set the state of a specific nested field, you have set the whole object. I did this by creating a variable, newState and spreading the contents of the current state into it first using the ES2015 spread operator. Then, I replaced the value of this.state.flag with the new value (since I set flag: value after I spread the current state into the object, the flag field in the current state is overridden). Then, I simply set the state of someProperty to my newState object.

5

I used this solution.

If you have a nested state like this:

   this.state = {
          formInputs:{
            friendName:{
              value:'',
              isValid:false,
              errorMsg:''
            },
            friendEmail:{
              value:'',
              isValid:false,
              errorMsg:''
            }
}

you can declare the handleChange function that copy current status and re-assigns it with changed values

handleChange(el) {
    let inputName = el.target.name;
    let inputValue = el.target.value;

    let statusCopy = Object.assign({}, this.state);
    statusCopy.formInputs[inputName].value = inputValue;

    this.setState(statusCopy);
  }

here the html with the event listener

<input type="text" onChange={this.handleChange} " name="friendName" />
Alberto Piras
  • 517
  • 6
  • 8
5

Create a copy of the state:

let someProperty = JSON.parse(JSON.stringify(this.state.someProperty))

make changes in this object:

someProperty.flag = "false"

now update the state

this.setState({someProperty})
ravibagul91
  • 20,072
  • 5
  • 36
  • 59
Dhakad
  • 83
  • 2
  • 6
5

Although nesting isn't really how you should treat a component state, sometimes for something easy for single tier nesting.

For a state like this

state = {
 contact: {
  phone: '888-888-8888',
  email: 'test@test.com'
 }
 address: {
  street:''
 },
 occupation: {
 }
}

A re-useable method ive used would look like this.

handleChange = (obj) => e => {
  let x = this.state[obj];
  x[e.target.name] = e.target.value;
  this.setState({ [obj]: x });
};

then just passing in the obj name for each nesting you want to address...

<TextField
 name="street"
 onChange={handleChange('address')}
 />
user2208124
  • 59
  • 1
  • 1
4

Not sure if this is technically correct according to the framework's standards, but sometimes you simply need to update nested objects. Here is my solution using hooks.

setInputState({
                ...inputState,
                [parentKey]: { ...inputState[parentKey], [childKey]: value },
            });
Ken
  • 693
  • 1
  • 8
  • 14
3

I am seeing everyone has given the class based component state update solve which is expected because he asked that for but I am trying to give the same solution for hook.

const [state, setState] = useState({
    state1: false,
    state2: 'lorem ipsum'
})

Now if you want to change the nested object key state1 only then you can do the any of the following:

Process 1

let oldState = state;
oldState.state1 = true
setState({...oldState);

Process 2

setState(prevState => ({
    ...prevState,
    state1: true
}))

I prefer the process 2 most.

moshfiqrony
  • 4,303
  • 2
  • 20
  • 29
3
setInputState((pre)=> ({...pre,[parentKey]: {...pre[parentKey], [childKey]: value}}));

I'd like this

JUGG
  • 41
  • 3
2

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.
Cory House
  • 14,235
  • 13
  • 70
  • 87
2

To make things generic, I worked on @ShubhamKhatri's and @Qwerty's answers.

state object

this.state = {
  name: '',
  grandParent: {
    parent1: {
      child: ''
    },
    parent2: {
      child: ''
    }
  }
};

input controls

<input
  value={this.state.name}
  onChange={this.updateState}
  type="text"
  name="name"
/>
<input
  value={this.state.grandParent.parent1.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent1.child"
/>
<input
  value={this.state.grandParent.parent2.child}
  onChange={this.updateState}
  type="text"
  name="grandParent.parent2.child"
/>

updateState method

setState as @ShubhamKhatri's answer

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const oldstate = this.state;
  const newstate = { ...oldstate };
  let newStateLevel = newstate;
  let oldStateLevel = oldstate;

  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      newStateLevel[path[i]] = event.target.value;
    } else {
      newStateLevel[path[i]] = { ...oldStateLevel[path[i]] };
      oldStateLevel = oldStateLevel[path[i]];
      newStateLevel = newStateLevel[path[i]];
    }
  }
  this.setState(newstate);
}

setState as @Qwerty's answer

updateState(event) {
  const path = event.target.name.split('.');
  const depth = path.length;
  const state = { ...this.state };
  let ref = state;
  for (let i = 0; i < depth; i += 1) {
    if (i === depth - 1) {
      ref[path[i]] = event.target.value;
    } else {
      ref = ref[path[i]];
    }
  }
  this.setState(state);
}

Note: These above methods won't work for arrays

Venugopal
  • 1,888
  • 1
  • 17
  • 30
2

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)}
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
1

If you want to set the state dynamically


following example sets the state of form dynamically where each key in state is object

 onChange(e:React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
    this.setState({ [e.target.name]: { ...this.state[e.target.name], value: e.target.value } });
  }
0

I found this to work for me, having a project form in my case where for example you have an id, and a name and I'd rather maintain state for a nested project.

return (
  <div>
      <h2>Project Details</h2>
      <form>
        <Input label="ID" group type="number" value={this.state.project.id} onChange={(event) => this.setState({ project: {...this.state.project, id: event.target.value}})} />
        <Input label="Name" group type="text" value={this.state.project.name} onChange={(event) => this.setState({ project: {...this.state.project, name: event.target.value}})} />
      </form> 
  </div>
)

Let me know!

Michael
  • 591
  • 8
  • 24
0
stateUpdate = () => {
    let obj = this.state;
    if(this.props.v12_data.values.email) {
      obj.obj_v12.Customer.EmailAddress = this.props.v12_data.values.email
    }
    this.setState(obj)
}
barbsan
  • 3,418
  • 11
  • 21
  • 28
  • 2
    I don't think this is correct, since `obj` is just a reference to the state, so through it you are mutating the actual `this.state` object – Ciprian Tomoiagă Jul 08 '19 at 09:26
0

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.

DragonKnight
  • 1,740
  • 2
  • 22
  • 35
0

Use this for multiple input control and dynamic nested name

<input type="text" name="title" placeholder="add title" onChange={this.handleInputChange} />
<input type="checkbox" name="chkusein" onChange={this.handleInputChange} />
<textarea name="body" id="" cols="30" rows="10" placeholder="add blog content" onChange={this.handleInputChange}></textarea>

the code very readable

the handler

handleInputChange = (event) => {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
        const newState = { ...this.state.someProperty, [name]: value }
        this.setState({ someProperty: newState })
    }
shavina
  • 9
  • 1
0

Here's a full example using nested state (one level) with the solution in this answer, for a component implemented as a class:

class CaveEditModal extends React.Component {

  // ...

  constructor(props, context) {
    super(props);
    this.state = {

      tabValue: '1',
      isModalOpen: this.props.isModalOpen,

      // ...
      caveData: {
        latitude: 1,
        longitude: 2      
      }
    };

    // ... 

    const updateNestedFieldEvent = fullKey => ev => { 
      
      var [parentProperty, _key] = fullKey.split(".", 2);

      this.setState({[parentProperty]: { ...this.state[parentProperty], [_key]: ev.target.value} });
    };
    // ...

    this.handleLatitudeChange = updateNestedFieldEvent('caveData.latitude');
    this.handleLongitudeChange = updateNestedFieldEvent('caveData.longitude');
  }

  render () {    
    return (
      <div>
         <TextField id="latitude" label="Latitude" type="number" value={this.state.caveData.latitude} onChange={this.handleLatitudeChange} />
         <TextField id="longitude" label="Longitude" type="number" value={this.state.caveData.longitude} onChange={this.handleLongitudeChange} />
         <span>lat={this.state.caveData.latitude} long={this.state.caveData.longitude}</span>
      </div>
    );
  };

}

Note that the state updater function updateNestedFieldEvent works only for one level nested object like a.b, not like a.b.c.

Alex P.
  • 1,140
  • 13
  • 27
0

For someone who read in 2022:

    constructor(props) {
        super(props);
        this.state = {
            someProperty: {
                flag: true
            }
            otherValues: '',
            errors: {}
        };

        this.handleInputChange = this.handleInputChange.bind(this);
    }

    handleInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;
        const someProperty = { ...this.state.someProperty };
        someProperty[name] = value;

        this.setState({
            someProperty: someProperty
        });
    }
    .......
Alamgir Khan
  • 87
  • 1
  • 2
  • 9
0

Use arrow function instead, this should do the trick.

setItems((prevState) => {
   prevState.nestedData = newNestedData;
   prevState.nestedData1 = newNestedData1;
});

don't forget to use the arrow function (prevState) => {update js assignment statements...}

Grigor Nazaryan
  • 567
  • 5
  • 18
0

React nested object state is definitely not a great design, to update the state first deep clone the current state, then update the state and then set the state.

If you don't deep clone, state will not be updated due to reference hence the component will not rerender.

  1. Clone the current state (you can use lodash _) in new variable stateUpdate

    const stateUpdate = _.cloneDeep(this.state)

  2. Update the property

    stateUpdate.someProperty.flag=false

  3. Set the state

    this.setState(stateUpdate);

-1

Something like this might suffice,

const isObject = (thing) => {
    if(thing && 
        typeof thing === 'object' &&
        typeof thing !== null
        && !(Array.isArray(thing))
    ){
        return true;
    }
    return false;
}

/*
  Call with an array containing the path to the property you want to access
  And the current component/redux state.

  For example if we want to update `hello` within the following obj
  const obj = {
     somePrimitive:false,
     someNestedObj:{
        hello:1
     }
  }

  we would do :
  //clone the object
  const cloned = clone(['someNestedObj','hello'],obj)
  //Set the new value
  cloned.someNestedObj.hello = 5;

*/
const clone = (arr, state) => {
    let clonedObj = {...state}
    const originalObj = clonedObj;
    arr.forEach(property => {
        if(!(property in clonedObj)){
            throw new Error('State missing property')
        }

        if(isObject(clonedObj[property])){
            clonedObj[property] = {...originalObj[property]};
            clonedObj = clonedObj[property];
        }
    })
    return originalObj;
}

const nestedObj = {
    someProperty:true,
    someNestedObj:{
        someOtherProperty:true
    }
}

const clonedObj = clone(['someProperty'], nestedObj);
console.log(clonedObj === nestedObj) //returns false
console.log(clonedObj.someProperty === nestedObj.someProperty) //returns true
console.log(clonedObj.someNestedObj === nestedObj.someNestedObj) //returns true

console.log()
const clonedObj2 = clone(['someProperty','someNestedObj','someOtherProperty'], nestedObj);
console.log(clonedObj2 === nestedObj) // returns false
console.log(clonedObj2.someNestedObj === nestedObj.someNestedObj) //returns false
//returns true (doesn't attempt to clone because its primitive type)
console.log(clonedObj2.someNestedObj.someOtherProperty === nestedObj.someNestedObj.someOtherProperty) 
Eladian
  • 958
  • 10
  • 29
-1

I know it is an old question but still wanted to share how i achieved this. Assuming state in constructor looks like this:

  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      user: {
        email: ""
      },
      organization: {
        name: ""
      }
    };

    this.handleChange = this.handleChange.bind(this);
  }

My handleChange function is like this:

  handleChange(e) {
    const names = e.target.name.split(".");
    const value = e.target.type === "checkbox" ? e.target.checked : e.target.value;
    this.setState((state) => {
      state[names[0]][names[1]] = value;
      return {[names[0]]: state[names[0]]};
    });
  }

And make sure you name inputs accordingly:

<input
   type="text"
   name="user.email"
   onChange={this.handleChange}
   value={this.state.user.firstName}
   placeholder="Email Address"
/>

<input
   type="text"
   name="organization.name"
   onChange={this.handleChange}
   value={this.state.organization.name}
   placeholder="Organization Name"
/>
Elnoor
  • 3,401
  • 4
  • 24
  • 39
-1

I do nested updates with a reduce search:

Example:

The nested variables in state:

state = {
    coords: {
        x: 0,
        y: 0,
        z: 0
    }
}

The function:

handleChange = nestedAttr => event => {
  const { target: { value } } = event;
  const attrs = nestedAttr.split('.');

  let stateVar = this.state[attrs[0]];
  if(attrs.length>1)
    attrs.reduce((a,b,index,arr)=>{
      if(index==arr.length-1)
        a[b] = value;
      else if(a[b]!=null)
        return a[b]
      else
        return a;
    },stateVar);
  else
    stateVar = value;

  this.setState({[attrs[0]]: stateVar})
}

Use:

<input
value={this.state.coords.x}
onChange={this.handleTextChange('coords.x')}
/>
Emisael Carrera
  • 617
  • 1
  • 7
  • 10
  • `a[b] = value` this mutates the nested state object returned by `this.state[attrs[0]]`. Do not mutate the current state objects. – Emile Bergeron Aug 30 '21 at 03:47
-1

This is my initialState

    const initialStateInput = {
        cabeceraFamilia: {
            familia: '',
            direccion: '',
            telefonos: '',
            email: ''
        },
        motivoConsulta: '',
        fechaHora: '',
        corresponsables: [],
    }

The hook or you can replace it with the state (class component)

const [infoAgendamiento, setInfoAgendamiento] = useState(initialStateInput);

The method for handleChange

const actualizarState = e => {
    const nameObjects = e.target.name.split('.');
    const newState = setStateNested(infoAgendamiento, nameObjects, e.target.value);
    setInfoAgendamiento({...newState});
};

Method for set state with nested states

const setStateNested = (state, nameObjects, value) => {
    let i = 0;
    let operativeState = state;
    if(nameObjects.length > 1){
        for (i = 0; i < nameObjects.length - 1; i++) {
            operativeState = operativeState[nameObjects[i]];
        }
    }
    operativeState[nameObjects[i]] = value;
    return state;
}

Finally this is the input that I use

<input type="text" className="form-control" name="cabeceraFamilia.direccion" placeholder="Dirección" defaultValue={infoAgendamiento.cabeceraFamilia.direccion} onChange={actualizarState} />
-1

If you are using formik in your project it has some easy way to handle this stuff. Here is the most easiest way to do with formik.

First set your initial values inside the formik initivalues attribute or in the react. state

Here, the initial values is define in react state

   state = { 
     data: {
        fy: {
            active: "N"
        }
     }
   }

define above initialValues for formik field inside formik initiValues attribute

<Formik
 initialValues={this.state.data}
 onSubmit={(values, actions)=> {...your actions goes here}}
>
{({ isSubmitting }) => (
  <Form>
    <Field type="checkbox" name="fy.active" onChange={(e) => {
      const value = e.target.checked;
      if(value) setFieldValue('fy.active', 'Y')
      else setFieldValue('fy.active', 'N')
    }}/>
  </Form>
)}
</Formik>

Make a console to the check the state updated into string instead of booleanthe formik setFieldValue function to set the state or go with react debugger tool to see the changes iniside formik state values.

Sharad Sharma
  • 89
  • 2
  • 12
-1

try this code:

this.setState({ someProperty: {flag: false} });
LoneWanderer
  • 3,058
  • 1
  • 23
  • 41
Soumya
  • 15
  • 1
  • 2
    But this will remove any other properties present in object referenced by `someProperty` and after the above code is executed only `flag` property will remain – Yashas Apr 25 '20 at 14:44
-1

you can do this with object spreading code :

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

this will work for more nested property

  • This is essentially the same answer as [this one](https://stackoverflow.com/a/45601371/1218980). Please avoid answering if you're not adding anything new or relevant to the thread. – Emile Bergeron Apr 20 '22 at 15:37
-1

There is another option and this works if there are multiple items in the list of objects: copy the object using this.state.Obj to a variable (say temp), use filter() method to traverse through the object and grab the particular element you want to change into one object(name it updateObj) and the remaining list of object into another object(name this as restObj). Now edit the contents of object you want to update creating a new item (say newItem). Then call this.setUpdate() and use spread operators to assing new list of objects to the parent object.

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


var temp=[...this.state.someProperty]
var restObj = temp.filter((item) => item.flag !== true);
var updateObj = temp.filter((item) => item.flag === true);

var newItem = {
  flag: false
};
this.setState({ someProperty: [...restObj, newItem] });
-1

You should pass new state to the setState. the reference of new state must be different than the old state.

So try this:

this.setState({
    ...this.state,
    someProperty: {...this.state.someProperty, flag: true},
})
ali orooji
  • 29
  • 2
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 19 '21 at 14:46
  • This is essentially the same as [this answer](https://stackoverflow.com/a/45601371/1218980). – Emile Bergeron Apr 20 '22 at 15:42