Reputation: 109
I've been using React for a bit now, and I've always updated an object's state using the spread operator as I remember reading you should directly change current state.
E.g now I write something like this:
const changeState = () => {
setState(prevState => {...prevState, existingKey: 'new value'})
}
But I've just come across a section "State Updates are Merged" in the react docs (https://reactjs.org/docs/state-and-lifecycle.html#state-updates-are-merged). If I'm understanding it correctly it's essentially saying I can just write this instead:
const changeState = () => {
setState({existingKey: 'new value'})
}
and all other keys in the object will remain as is, only the key I have specified will be updated in state. Am I reading this correctly? I've never seen this on stackoverflow, I always see people saying to use spread operators, is there a reason why?
Upvotes: 4
Views: 20055
Reputation: 202836
The reason the functional state update and shallow copying is recommended is that is works for all use cases. The state-updates-are-merged link you reference actually applies to class-based component's this.setState
method.
Enqueueing multiple updates within a single render cycle, loops, etc...
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
The net result here is only a single count + 1
update as each update overwrote the previous one.
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
this.setState(prevState => ({ count: prevState.count + 1 }));
The net result here is now count + 3
.
Referencing the previous state in callbacks to avoid stale enclosures.
componentDidMount() {
setInterval(() => this.setState({ count: this.state.count + 1 }), 1000);
}
Here the initial count
state is closed over in callback scope and will never update.
componentDidMount() {
setInterval(() => this.setState(prevState => { count: prevState.count + 1 }), 1000);
}
Only the root properties are merged, more deeply nested state being updated also needs to be shallow copied.
this.setState(prevState => ({
...prevState,
property: {
...prevState.property,
nestedProperty: 'new value',
},
}));
On the off-hand chance you are using the useState
hook, state updates are not shallowly merged.
Unlike the setState method found in class components, useState does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
const [state, setState] = useState({}); setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
If the next state depends on the previous state in any way, use a functional state update. Otherwise, the regular update is sufficient.
Upvotes: 6
Reputation: 7
//Use react hooks for the functional component
import React, { useState, useEffect } from 'react';
//Suppose a user object with name, age, gender properties.
let [userData, setUserData] = useState({
name: "Somnath Jana",
age:27
gender: "Male"
})
useEffect(()=>{
userData.age = 28
setUserData({...userData})
},[])
Upvotes: -1
Reputation: 1285
For Functional components, the syntax would be:
const changeState = () => {
setState(prevState => {...prevState, existingKey: 'new value'})
}
For Class components, the syntax would be:
const changeState = () => {
// Notice the use of this
this.setState({existingKey: 'new value'})
}
Upvotes: 1