I recently learned the hard and long way about shallow and deep cloning of objects and the relationship that exists between shallow cloned objects after doing something like this:
this.state = {
obj: props.obj,
}
let { obj } = this.state
obj.x = event.target.value
if (obj !== this.props.obj) {
this.setState({obj})
}
After instead assigning a deep clone of props.obj to state.obj this works like it should. I started looking around and found this pattern throughout my code:
let obj = this.state.obj
obj.x = 'test'
this.setState({obj});
Which has been working fine without me realizing that I've actually been mutating this.state directly. It hasn't been an issue because I'm almost always calling setState right after with the changes. I was following this pattern as a means to avoid mutating state directly.
What is the preferred method for making changes to state like this without altering the state object directly?
Is there a rule/guideline/best practice for when to deep clone an object?
Is there any downside to always deep cloning in an attempt to avoid it all?
Maybe these can be boiled down to one question which I don't know how to word.
I am not 100% sure
this.setState({
obj: { ...this.state.obj,
x: event.target.value
}
});
Related
I just start picking up react.js so I went through a lot of tutorials and I've stumbled upon this bit which basically meant to delete an item from the state.
this is how the guy introduced to me the delete function
delTodo = id => {
this.setState({
todos: [...this.state.todos.filter(todo => todo.id !== id)]
});
};
Since I am not so familiar with javascript I had a hard time figuring out what the ... operator is doing and why exactly is he using it in the given scenario. So in order to have a better understanding of how it works, I played a bit in the console and I've realised that array = [...array]. But is that true?
Is this bit doing the same exact thing as the one from above?
delTodo = id => {
this.setState({
todos: this.state.todos.filter(todo => todo.id !== id)
});
};
Could someone more experienced clarify to me why he has chosen to be using that approach instead of the one I've come up with?
As per the documentation:
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
reactjs.org/docs/react-component.html#state
So, in the example from the tutorial you've mentioned, you wouldn't need to make a copy of the array to update your state.
// GOOD
delTodo = id => {
this.setState({
todos: this.state.todos.filter(...)
})
}
Array.filter method creates a new array and does not mutate the original array, therefore it won't directly mutate your state. Same thing applies to methods such as Array.map or Array.concat.
If your state is an array and you're applying methods that are mutable, you should copy your array.
See more to figure out which Array methods are mutable:
doesitmutate.xyz
However, if you were to do something like the following:
// BAD
delTodo = id => {
const todos = this.state.todos
todos.splice(id, 1)
this.setState({ todos: todos })
}
Then you'd be mutating your state directly, because Array.splice changes the content of an existing array, rather than returning a new array after deleting the specific item. Therefore, you should copy your array with the spread operator.
// GOOD
delTodo = id => {
const todos = [...this.state.todos]
todos.splice(id, 1)
this.setState({ todos: todos })
}
Similarly with objects, you should apply the same technique.
// BAD
updateFoo = () => {
const foo = this.state.foo // `foo` is an object {}
foo.bar = "HelloWorld"
this.setState({ foo: foo })
}
The above directly mutates your state, so you should make a copy and then update your state.
// GOOD
updateFoo = () => {
const foo = {...this.state.foo} // `foo` is an object {}
foo.bar = "HelloWorld"
this.setState({ foo: foo })
}
Hope this helps.
Why use the spread operator at all?
The spread operator ... is often used for creating shallow copies of arrays or objects. This is especially useful when you aim to avoid mutating values, which is encouraged for different reasons. TLDR; Code with immutable values is much easier to reason about. Long answer here.
Why is the spread operator used so commonly in react?
In react, it is strongly recommended to avoid mutation of this.state and instead call this.setState(newState). Mutating state directly will not trigger a re-render, and may lead to poor UX, unexpected behavior, or even bugs. This is because it may cause the internal state to differ from the state that is being rendered.
To avoid manipulating values, it has become common practice to use the spread operator to create derivatives of objects (or arrays), without mutating the original:
// current state
let initialState = {
user: "Bastian",
activeTodo: "do nothing",
todos: ["do nothing"]
}
function addNewTodo(newTodo) {
// - first spread state, to copy over the current state and avoid mutation
// - then set the fields you wish to modify
this.setState({
...this.state,
activeTodo: newTodo,
todos: [...this.state.todos, newTodo]
})
}
// updating state like this...
addNewTodo("go for a run")
// results in the initial state to be replaced by this:
let updatedState = {
user: "Bastian",
activeTodo: "go for a run",
todos: ["do nothing", "go for a run"]
}
Why is the spread operator used in the example?
Probably to avoid accidental state mutation. While Array.filter() does not mutate the original array and is safe to use on react state, there are several other methods which do mutate the original array, and should not be used on state. For example: .push(), .pop(),.splice(). By spreading the array before calling an operation on it, you ensure that you are not mutating state. That being said, I believe the author made a typo and instead was going for this:
delTodo = id => {
this.setState({
todos: [...this.state.todos].filter(todo => todo.id !== id)
});
};
If you have a need to use one of the mutating functions, you can choose to use them with spread in the following manner, to avoid mutating state and potentially causing bugs in your application:
// here we mutate the copied array, before we set it as the new state
// note that we spread BEFORE using an array method
this.setState({
todos: [...this.state.todos].push("new todo")
});
// in this case, you can also avoid mutation alltogether:
this.setState({
todos: [...this.state.todos, "new todo"]
});
As .filter gives you a new array (than mutating the source array), it is acceptable and results in the same behaviour, making spreading redundant here.
What's not acceptable is:
const delIndex = this.state.todos.findIndex(todo => todo.id !== id);
this.state.todos.splice(delIndex, 1); // state mutation
this.setState({
todos: this.state.todos
});
slice is fine though:
const delIndex = this.state.todos.findIndex(todo => todo.id !== id);
this.setState({
todos: [
...this.state.todos.slice(0, delIndex),
...this.state.todos.slice(delIndex + 1)
]
});
If you mutate state (which keeps ref same) React may not be able to ascertain which part of your state actually changed and probably construct a tree on next render which is different from expected.
The spread operator that the guy's code is applying causes the array returned from the filter function to be copied again.
Since [.filter is returning a new array][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter], you will already avoid mutating the array in state directly. It seems like the spread operator may be redundant.
One thing I wanted to point out too is while the values in a copied array may be the same as the values in an old array (array = [...array]), the instances change so you wouldn't be able to use '===' or '==' to check for strict equivalency.
const a = ['a', 'b']
const b = [...a]
console.log(a === b) // false
console.log(a == b) // false
console.log(a[0] === b[0]) // true
console.log(a[1] === b[1]) // true
Hope this helps!
I am very confused.
Say I have a messages array that is structured like this:
this.state.messages = [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
];
and the user wants to edit the third message. React says to be immutable. That is, it is bad to do this:
this.state.messages[2].body = "test";
this.forceUpdate();
The problem with this is that React can't tell the messages state variable changed, because its reference is still the same. Firstly, does this even matter? I'm calling forceUpdate anyways, so it's going to re-render regardless. Shouldn't this only matter if you are using a PureComponent or a custom shouldComponentUpdate function?
Okay, well let's say it does matter (for some reason). Then it's advised to do a deep copy so that React knows the object changed. But how deep is deep enough?
For example, is it enough to do this?
let x = this.state.messages.slice(0);
x[2].body = "test";
this.setState({ messages: x });
This copies over the references to all the array elements, but it's not entirely deep! Because (before we do setState of course), this.state.messages[0].body === x[0].body. The string was not copied. It is still sharing internal state with the previous object. It is not a fully deep copy.
Okay, but if we don't need to do a full deep copy and the only thing that matters is that the parent node reference changes, then can we not just cheat and do this?
this.state.messages = { a: [
{ message_id: 0, body: "hello", author: "john" },
{ message_id: 1, body: "how", author: "lilly" },
{ message_id: 2, body: "are", author: "abe" },
{ message_id: 3, body: "you", author: "josh" }
]};
let x = { a: this.state.messages.a };
this.setState({ messages: x });
Now instead of copying over all the message references (of which there may be thousands), it is a single pointer change. But both messages are not deep copies. If the slice version is okay then should this not also be okay? Neither are deep copies it seems.
And if this latter method is okay, is there a more elegant way of writing it such that you don't need an arbitrary container object (a in this case)? A way to just change the pointer somehow to signal to React that the contents changed?
Edit: Okay, I don't think I explained myself well enough. Sorry, let me try again. Forget about performance for a second. What I'm wondering is: do you need to do deep clones or not for state updates? For example, what would be the proper way to update a body in the messages array in the example posted above? .slice(0) is not a deep copy, because it is still sharing internal structure (it is only copying references). Is this okay? Or do you need to do a deep copy to be proper? And if it IS okay, then should it not also be okay to just have a wrapper object and just only change THAT pointer instead?
Further Edit: I'm not sure if I'm just not explaining myself properly or if I am missing something very obvious. Does React need deep clones or not? I feel like this is an either-or sort of thing. I find it very doubtful that React requires 70% of a deep clone, but not 30%. Either it needs a full deep clone or it doesn't. If it doesn't, then shouldn't just changing a single wrapper pointer be enough? And if it isn't, then isn't slice(0) and Object.assign also not enough, because they are also shallow clones? In the case of these clones the internal objects still maintain the same structure (for example, the String references don't change).
You shouldn't be using force updates.
You shouldn't mutate the state object - it should be immutable for a reason (easier to debug, better performance, etc)
https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability
https://medium.com/#kkranthi438/dont-mutate-state-in-react-6b25d5e06f42
https://daveceddia.com/why-not-modify-react-state-directly/
Do not worry about performance unless there is a real performance issue - premature optimizations like this could actually harm performance, and slow your development (it makes your app harder to debug).
"Thousands of objects" is nothing to modern day computers / mobile devices. There are many other things you could try before breaking the immutability convention - you could try to split your state in smaller chunks, use selectors, better organize your components so not everything has to be re-rendered when only a small chunk of your state changed.
It's usually better to use flat / simple objects for your state - so it's much easier to create new copies of them. (when you do Object.assign or { ...foo } you are only getting shallow copies.) If you can't do away with deep nested objects you could try to add a 3rd party library like immutability-helper, or lodash.cloneDeep
In short, only update your state with setState - try to avoid forceUpdate and things like this.state.foo = bar. Even if some deeply nested objects are still referencing their old state, as long as you are following the rules you should be ok in most cases.
However do try to keep your state objects shallow whenever possible.
In your example, you mentioned this.state.messages[0].body === x[0].body the string is indeed copied. Strings are always copied in JS. your expression is comparing two string values - not their references.
// Given:
let obj = { foo: { bar: 'baz' } };
let fooObj = obj.foo; // fooObj is a reference to obj.foo;
let str = obj.foo.bar; // str is a COPY of the string 'baz';
str === fooOjb.bar // true, you are comparing their values, not references.
obj.foo.bar = 'baz2';
fooObj === obj.foo; // true, because you are comparing their references.
str === obj.foo.bar // false - str value does not change when obj.foo.bar changes.
Is immutability absolutely required in react?
Short answer: NO. You can do whatever you want. React is not going to throw errors at you when parts of your new state is still referencing to the old state.
However, never mutate your state directly. Always do it via setState. Don't worry too much about if your state object is 100% deep cloned or not. As long as you can make sure no parts of your app modifies your state, React can handle the rest.
There is a really good example of storing Todo list as a map of todo items and list of its' ids:
const state = {
todos: {
0: "Buy a milk",
1: "Buy a bread",
},
todosList: [0, 1]
}
Maybe you should split your state and store messages as map with ID as a key. It gives you a O(1) time for adding, changing and deleting messages.
And if you would store a messages map as a separate state, you'll be able to change any message like this:
this.setState({
[messageId]: newValue
})
Stop mutating your data!
One of React's primary features is doing change detection on data held in the 'state' object. The key to this change detection is to make sure you are always using previous state, modifying whatever data you need and React will handle the change detection, then update the UI accordingly and as efficiently as possible.
this.setState((prevState)=> {
return {
...prevState,
messages: prevState.messages.map((obj,i)=> i === 2 ? { ...obj, body:"test" } : obj)
}
})
I've read that it is not advisable to update state directly, e.g:
this.state.array = ['element'];
But, for example, consider this state:
this.state = {
array: [],
}
updateArray = element => {
temp = this.state.array;
temp.push(element);
this.setState({array: temp});
}
Isn't this essentialy the same as the first example? We are setting temp to point to this.state.array, thus mutating it and then overwriting it with this.setState().
I've seen this example in various tutorials, Wouldn't it be 'better' to make a copy of the array?
Check out the docs here, https://reactjs.org/docs/state-and-lifecycle.html.
According to the React team. Setting the state directly will not re-render a component. You must use setState.
As far as using a temp variable when setting state, that is used when something needs to be done to 'element' before setting the state variable. As setState can be called asynchronously. It is acceptable in your case to just do
updateArray = (element) => {
this.setState({ array: [ ...this.state.array, element ] });
}
Yes, you are right it's the same thing. You should read more about pass by value and pass by reference in JS.
When we say
let x = some.array
We are copying a reference(remember copy and paste as shortcut)? Both the things actually point to the same real thing and takes no extra space.
You should instead use some JS that can do this for you.
let x = some.array.slice()
This creates real copy of the array. On modern ES2015 you can do
let x = [...some.array]
a more elegent way.
I know how to do this but trying this way and not sure why it wont work?
drawCard = () => {
const deck = this.state.cards;
deck.shift();
console.log(deck, 'deck'); //this is correctly logging one less everytime
this.setState({cards: deck})
}
cards is just an of objects
so even though the function is being called and the console log is working, why is it not updating state?
(console.log(state.cards.length) always returns 3)
Don't use methods that mutate the state (for instance, Array#shift). You could instead use Array#slice to return a shallow copy of a portion of the array:
drawCard = () => {
this.setState({ cards: this.state.cards.slice(1) })
}
The problem arises because you are directly modifying the state. Although you are assigning the state to 'deck' remember that in Javascript arrays and objects are references. So when you are modifying deck you are actually trying to modify the state itself. You can use other methods to create a copy of the states values and modify that.
You may find this helpful:
React - Changing the state without using setState: Must avoid it?
in react, you can't just change the state,
you need to create a copy of your state before updating,
the easiest way to do it is just replace this:
const deck = this.state.cards;
into this:
const deck = [...this.state.cards];
We all know the React docs say to never mutate this.state directly. I guess I have a lingering question on state arrays and immutability:
If I have an array of objects held in state, should I always use the immutability helper as in this question when mutating that array in any way?
Or is it perfectly acceptable to use [].concat() or [].slice() as answered here and here?
I ask this question again because [].concat() and [].slice() do return new arrays, but only shallow copy arrays. If I change an element of the array, it will change the array in state as well, violating the first rule of Reactjs State (I've been watching too much FMA:Brotherhood):
var arr1 = [{ name : "bill" }, { name : "chet" }];
var arr2 = arr1.slice();
// this changes both arrays
arr2[0].name = "kevin";
// check
(arr1[0].name === arr2[0].name) // => true; both are "kevin"
(arr1[0] === arr2[0]) // => true; only shallow copy
// this changes arr2 only
arr2.push({ name : "alex" });
// check again
(arr1.length === arr2.length) // => false;
(arr1[0].name === arr2[0].name) // => still true;
(arr1[0] === arr2[0]) // => still true;
I understand that the addon update is most commonly used when overriding shouldComponentUpdate, but for what I'm doing I don't need to override that function; I just need to mutate objects in the array held in state by either adding new elements to the array (solved using concat or slice), or by changing properties of existing elements (solved by using React.addons.update).
TL;DR
If not overriding shouldComponentUpdate, when should I use React.addons.update over [].slice() or [].concat() to mutate an array of objects stored in state?
You primarily need to consider boundaries. If you only use this array in the component that owns it, and optionally pass it down as props: it doesn't matter. You can modify it in any way, and unless the other components have some serious problems, they'll never know the difference because they get the new props when your component updates its state.
If you pass this array to some kind of api, such as a flux action creator or a function passed to you as a prop, then you might run into some very serious and hard to track down bugs. This is where immutability is important.
For example, in this case you might want to check some property of a person in the callback, however if it's possible for people[0].name to have changed, you have a race condition.
function savePeople(people){
$.post('/people/update', people, function(resp){
if (people[0].name === ...) {
// ...
}
});
}