Reputation: 251
Let's say I have a Person class:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
And then I instantiate a Person object in the state:
export default class SomeComponent extends React.Component {
constructor() {
super();
this.state = {
cena: new Person('John Cena', 40)
}
}
render() {
const { name, age } = this.state.cena;
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
</div>
)
}
}
Let's say we have a button that increments Cena's age by one. Is it safe to set the state with an entirely new Person object although we only need to change the age?
this.setState(prevState => ({
cena: new Person(prevState.cena.name, prevState.cena.age + 1)
}));
I did test this with Chrome's paint flashing tool. When I set this.state.cena
with a brand new Person with incremented age, only the Age paragraph gets repainted the Name paragraph stays intact.
However, I am still a bit concerned. Should I rely on React to handle efficient shallow comparison, or should I manually change only what's need to be changed?
I don't even know how I could achieve that. If I try to only setState
the age, I still have to do something like:
this.setState(prevState => ({
cena: { ...prevState, age: prevState.age + 1 }
}));
Which is exactly the same as creating a new Person through its constructor. To update only the age 'efficiently', I have to spread Person to the first level of the state.
constructor() {
super();
this.state = {
...new Person('John Cena', 40)
}
}
Which is terrible because I might need to create another Person later on.
So, I'll rephrase my question: should I setState an entirely new object and rely on React to handle shallow comparison?
Upvotes: 4
Views: 4927
Reputation: 4306
Your state will only live in the component (SomeComponent) unless it's sent back through a function property.
Why not go with your last proposed solution, spreading the new Person instance. If it seems odd, maybe try instantiating the class outside of the constructor.
Example:
let person = new Person('Name', 40);
this.state = {
...this.person,
};
console.log(this.state); // outputs the instance members
or
let person = new Person('Name', 40);
this.state = {
person: this.person
};
console.log(this.state.person); // outputs the instance members
If your component state needs to store multiple Person instances, you could store them in an array:
let personA = new Person('A', 21);
let personB = new Person('B', 25);
this.state = {
persons: [personA, personB]
}
And to update the person, treat the state as if it's immutable:
const persons = this.state.persons.slice() //copy the array
persons[1].name = 'C' //manipulate Person 'B' instance
this.setState({ persons }) //set the new state
Upvotes: 0
Reputation: 85545
Generally, I would not create class object for storing state. I would just set the state as object for its simplicity:
this.state = {
cena: {
name: '',
age: ''
}
}
Now, I can change the state simply like this:
this.setState({
cena: {
...this.state.cena,
age: this.state.cena.age+1
}
})
Upvotes: -1
Reputation: 66244
Should I rely on React to handle efficient shallow comparison, or should I manually change only what's need to be changed?
Whatever you pass into setState is merged with the current state. After that merge, if state has changed, React will rerender the components which use this state.
All three of your examples are functionally identical and you should use the one you find most readable.
In general, you can rely on this to be very performant. You can also get improved performance by using PureComponent and/or functional components where appropriate.
Upvotes: 5