Is it at all possible to update object’s properties with setState
?
Something like:
this.state = { jasper: { name: 'jasper', age: 28 }, }
I have tried:
this.setState({jasper.name: 'someOtherName'});
and this:
this.setState({jasper: {name: 'someothername'}})
The first results in a syntax error and the second just does nothing. Any ideas?
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
There are multiple ways of doing this, since state update is a async operation, so to update the state object, we need to use updater function with setState
.
1- Simplest one:
First create a copy of jasper
then do the changes in that:
this.setState(prevState => { let jasper = Object.assign({}, prevState.jasper); // creating copy of state variable jasper jasper.name = 'someothername'; // update the name property, assign a new value return { jasper }; // return new object jasper object })
Instead of using Object.assign
we can also write it like this:
let jasper = { ...prevState.jasper };
2- Using spread syntax:
this.setState(prevState => ({ jasper: { // object that we want to update ...prevState.jasper, // keep all other key-value pairs name: 'something' // update the value of specific key } }))
Note: Object.assign
and Spread Operator
creates only shallow copy, so if you have defined nested object or array of objects, you need a different approach.
Updating nested state object:
Assume you have defined state as:
this.state = { food: { sandwich: { capsicum: true, crackers: true, mayonnaise: true }, pizza: { jalapeno: true, extraCheese: false } } }
To update extraCheese of pizza object:
this.setState(prevState => ({ food: { ...prevState.food, // copy all other key-value pairs of food object pizza: { // specific object of food object ...prevState.food.pizza, // copy all pizza key-value pairs extraCheese: true // update value of specific key } } }))
Updating array of objects:
Lets assume you have a todo app, and you are managing the data in this form:
this.state = { todoItems: [ { name: 'Learn React Basics', status: 'pending' }, { name: 'Check Codebase', status: 'pending' } ] }
To update the status of any todo object, run a map on the array and check for some unique value of each object, in case of condition=true
, return the new object with updated value, else same object.
let key = 2; this.setState(prevState => ({ todoItems: prevState.todoItems.map( el => el.key === key? { ...el, status: 'done' }: el ) }))
Suggestion: If object doesn’t have a unique value, then use array index.
Method 2
This is the fastest and the most readable way:
this.setState({...this.state.jasper, name: 'someothername'});
Even if this.state.jasper
already contains a name property, the new name name: 'someothername'
with be used.
Method 3
Use spread operator and some ES6 here
this.setState({ jasper: { ...this.state.jasper, name: 'something' } })
Method 4
I know there are a lot of answers here, but I’m surprised none of them create a copy of the new object outside of setState, and then simply setState({newObject}). Clean, concise and reliable. So in this case:
const jasper = { ...this.state.jasper, name: 'someothername' }
this.setState(() => ({ jasper }))
Or for a dynamic property (very useful for forms)
const jasper = { ...this.state.jasper, [VarRepresentingPropertyName]: 'new value' }
this.setState(() => ({ jasper }))
Method 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. Make sure to use the same name used into state object (in this case ‘friendName’)
<input type="text" onChange={this.handleChange} " name="friendName" />
Method 6
try this,it should work fine
this.setState(Object.assign(this.state.jasper,{name:'someOtherName'}));
Method 7
The first case is indeed a syntax error.
Since I can’t see the rest of your component, it’s hard to see why you’re nesting objects in your state here. It’s not a good idea to nest objects in component state. Try setting your initial state to be:
this.state = { name: 'jasper', age: 28 }
That way, if you want to update the name, you can just call:
this.setState({ name: 'Sean' });
Will that achieve what you’re aiming for?
For larger, more complex data stores, I would use something like Redux. But that’s much more advanced.
The general rule with component state is to use it only to manage UI state of the component (e.g. active, timers, etc.)
Check out these references:
- https://facebook.github.io/react/docs/react-component.html#state
- https://facebook.github.io/react/docs/state-and-lifecycle.html
Method 8
this is another solution using immer immutabe utility, very suited for deeply nested objects with ease, and you should not care about mutation
this.setState( produce(draft => { draft.jasper.name = 'someothername' }) )
Method 9
In case of updating an object where keys are string
e.g. let say your state object is
serviceDays: { Sunday: true, Monday: true, Tuesday: false, Wednesday: true, Thurday: false, Friday: true, Saturday: true }
so you can update in following way
const onDayClick = day => { const { serviceDays } = this.state this.setState(prevState => ({ serviceDays: { ...prevState.serviceDays, [day]: serviceDays[day] ? false : true } })) }
Method 10
Using hook we can do following way
const [student, setStudent] = React.useState({name: 'jasper', age: 28}); setStudent((prevState) => ({ ...prevState, name: 'newName', }));
Method 11
Another option: define your variable out of the Jasper object and then just call a variable.
Spread operator: ES6
this.state = { jasper: { name: 'jasper', age: 28 } } let foo = "something that needs to be saved into state" this.setState(prevState => ({ jasper: { ...jasper.entity, foo } })
Method 12
You can try with this:
this.setState(prevState => { prevState = JSON.parse(JSON.stringify(this.state.jasper)); prevState.name = 'someOtherName'; return {jasper: prevState} })
or for other property:
this.setState(prevState => { prevState = JSON.parse(JSON.stringify(this.state.jasper)); prevState.age = 'someOtherAge'; return {jasper: prevState} })
Or you can use handleChage function:
handleChage(event) { const {name, value} = event.target; this.setState(prevState => { prevState = JSON.parse(JSON.stringify(this.state.jasper)); prevState[name] = value; return {jasper: prevState} }) }
and HTML code:
<input type={"text"} name={"name"} value={this.state.jasper.name} onChange={this.handleChange} /> <br/> <input type={"text"} name={"age"} value={this.state.jasper.age} onChange={this.handleChange} />
Method 13
Create a state object
this.state = { objName: { propertyOne: "", propertyTwo: "" } };
Update state using setState
this.setState(prevState => ({ objName: { ...prevState.objName, propertyOne: "Updated Value", propertyTwo: "Updated value" } }));
Method 14
Simple and dynamic way.
This will do the job, but you need to set all the ids to the parent so the parent will point to the name of the object, being id = “jasper” and name the name of the input element = property inside of the object jasper.
handleChangeObj = ({target: { id , name , value}}) => this.setState({ [id]: { ...this.state[id] , [name]: value } });
Method 15
Without using Async and Await Use this…
funCall(){ this.setState({...this.state.jasper, name: 'someothername'}); }
If you using with Async And Await use this…
async funCall(){ await this.setState({...this.state.jasper, name: 'someothername'}); }
Method 16
You can try with this:
(Note: name of input tag === field of object)
<input name="myField" type="text" value={this.state.myObject.myField} onChange={this.handleChangeInpForm}> </input> ----------------------------------------------------------- handleChangeInpForm = (e) => { let newObject = this.state.myObject; newObject[e.target.name] = e.target.value; this.setState({ myObject: newObject }) }
Method 17
Using hooks in Functional Component:
const [state, setState] = useState({jasper: { name: 'jasper', age: 28 }}) const nameChangeHandler = () => { setState(prevState => ({ ...prevState, prevState.jasper.name = "Anurag", prevState.jasper.age = 28 }) ) }
In these cases It is recommended to use callback-based approach to update the state , because using this approach it is ensured that previously states are fully updated and we’re updating based on previously updated state.
Method 18
Also, following Alberto Piras solution, if you don’t want to copy all the “state” object:
handleChange(el) { let inputName = el.target.name; let inputValue = el.target.value; let jasperCopy = Object.assign({}, this.state.jasper); jasperCopy[inputName].name = inputValue; this.setState({jasper: jasperCopy}); }
Method 19
Try with this:
const { jasper } = this.state; //Gets the object from state jasper.name = 'A new name'; //do whatever you want with the object this.setState({jasper}); //Replace the object in state
Method 20
By using the input html input name attribute we can have a more dynamic approach in order to update an object properties.
<input type="text" name="fname" handleChange={(e: any) => { updatePerson(e) }}/> <input type="text" name="lname" handleChange={(e: any) => { updatePerson(e) }}/>
React / TSX
object.assign
const [person, setPerson] = useState<IPerson>({}); function updatePerson(e: React.ChangeEvent<HTMLInputElement>): void { const { name, value } = e.currentTarget; setPerson(prevState => { const newState = Object.assign(person, { [name]: value }) return { ...prevState, ...newState }; }); }
Method 21
This setup worked for me:
let newState = this.state.jasper; newState.name = 'someOtherName'; this.setState({newState: newState}); console.log(this.state.jasper.name); //someOtherName
Method 22
Your second approach doesn’t work because {name: 'someothername'}
equals {name: 'someothername', age: undefined}
, so theundefined
would overwrite original age value.
When it comes to change state in nested objects, a good approach would be Immutable.js
this.state = { jasper: Record({name: 'jasper', age: 28}) } const {jasper} = this.state this.setState({jasper: jasper.set(name, 'someothername')})
Method 23
Sample FC:
const [formData, setformData] = useState({ project_admin_permissions: { task_forms: false, auto_assign_rules: false, project_notes: true, alerts: false, update_criteria: true, project_flow: false, reports: false, } }) const handleChangeCheckBox = (e) => { setformData({ ...formData, project_admin_permissions: { ...formData.project_admin_permissions, [e.target.name]: e.target.checked } }) }
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0