score:649
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
.
score:-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)
score:-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"
/>
score:-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')}
/>
score:-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} />
score:-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 boolean
the formik setfieldvalue
function to set the state or go with react debugger tool to see the changes iniside formik state values.
score:-1
try this code:
this.setstate({ someproperty: {flag: false} });
score:-1
you can do this with object spreading code :
this.setstate((state)=>({ someproperty:{...state.someproperty,flag:false}})
this will work for more nested property
score:-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] });
score:-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},
})
score: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!
score: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)
}
score: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.
score: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 })
}
score: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 } });
}
score:2
two other options not mentioned yet:
- 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.
- 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.
score: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
score: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)}
score: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.
score:4
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})
score: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 },
});
score: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" />
score: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')}
/>
score: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.
score:8
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 got:
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 { usestatelink } from '@hookstate/core'
const state = usestatelink({ someproperty: { flag: true, othernestedprop: 1 }, otherprop: 2 })
get the field to render:
state.nested.someproperty.nested.flag.get()
// or
state.get().someproperty.flag
set the nested field:
state.nested.someproperty.nested.flag.set(false)
here is the hookstate example, where the state is deeply / recursively nested in tree-like data structure.
score: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},
});
score:18
we use immer https://github.com/mweststrate/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.
score:22
const newstate = object.assign({}, this.state);
newstate.property.nestedproperty = "new value";
this.setstate(newstate);
score: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.
score:105
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.
anyway here you can see that nested purechildclass
is not rerendered due to props failing to propagate.
score:146
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.
score:177
to write it in one line
this.setstate({ someproperty: { ...this.state.someproperty, flag: false} });
Source: stackoverflow.com
Related Query
- How to update nested state properties in React
- How to update a nested state properties with e.target.name in React
- How to update nested state properties in react using immutable way
- React hooks: How do I update state on a nested object with useState()?
- How to update the state of nested json object in react using hooks
- How to update a nested react state
- how to update nested state array and then return the entire array react
- How to update a state of infinite nested array in React JS
- dynamic form in react how to update nested state field in react.js
- How to display and update deep nested values in React state
- React - how to update state with dynamic properties without defining it in state first?
- how to update react state to add an element in array, which is nested array?
- How do I stop nested React components that dispatch Redux actions which update state from getting stuck in an infinite loop?
- How to update objects nested in React state
- How to update array state in react native?
- How to update react state without re-rendering component?
- How to update object in React state
- how to update multiple state at once using react hook react.js
- How to change multiple properties of a state in react (at the same time)?
- How to update the state of a sibling component from another sibling or imported component in REACT
- How to update the router state on history.replace in react router?
- What's the best alternative to update nested React state property with setState()?
- How to update (re-render) the child components in React when the parent's state change?
- How to update multiple state properties with immer.js
- How to update React component after changing state through redux?
- React Native: How to stop map markers from re-rendering on every state update
- How to properly update a react native swiper component when state changes?
- How to update nested states in React, should state be immutable?
- How to test state update and component rerender after async call in react
- How to test redux state update with react testing library and jest
More Query from same tag
- Rendering a React component inside a Bootstrap popover
- if the json is duplicate, return the first element
- Map function with Graphql, React
- GraphQL Unexpected end of JSON input
- React Bootstrap Refusing to Render
- How can I make a "function inside a react-modal element work"?
- Redux trigger action from the component
- How to retrieve images from server path using reactjs
- When login button is clicked the page is Redirecting but not rendering the page
- React-Redux action not taking effect
- React is running on Docker however isn't accessible to public IP
- React nested form values
- iOS 11 JavaScript can't find variable `globalThis`
- Why my components don't display on my React page?
- how to use antd in electron react boilerplate?
- Clear State After Submit React
- Align react table to centre of page
- I can't stop loop in react js when i'm using ccomponentDidUpdate with axios
- Get an Error that .filter is not a function
- useEffect run infinitely
- Make an unclickable Menu Item
- Load external script which has document.write using ReactJS
- How to parse JWT token from request [Next-Auth]
- React-Redux: Post action fails with a 500 error
- Unterminated jsx content while using browserroutes?
- Using mjackson's expect spyOn on function from React component's props
- Image is not Show in React js . Whe I want to show image it's not showing . without This Every Thing is Okay
- Gatsby: getImage returns undefined
- How to use router links inside switch
- How to autofocus on the USD value when 3 values are entered in reactjs?