Modifying the first of item in an array – two ways:
- one modifies the items directly
- one creates a copy of the array
Which way is the correct way to do it?
const [testArray, setTestArray] = React.useState([{ name: 'value0' }, { name: 'value1' }]);
const handleTest = () => {
setTestArray((prev) => {
const firstItem = prev[0];
const updatedFirstItem = { ...firstItem, name: 'newValue' };
prev[0] = updatedFirstItem;
return prev;
});
};
const handleTest2 = () => {
setTestArray((prev) => {
const copyOfArray = [...prev];
const firstItem = copyOfArray[0];
const updatedFirstItem = { ...firstItem, name: 'newValue' };
copyOfArray[0] = updatedFirstItem;
return copyOfArray;
});
};
From a purely "at the surface"… looking array and its values… both should return an array with the same values (to be clear, not talking about objects nor references in memory, just looking at the surface of an array and its values).
But from a Virtual DOM / and React reconciliation perspective, is there a preferred way to do this?
2
Answers
You want the second one - make a copy and then update the copy.
From the React docs:
So when working with array objects - do the ones on the right column.
push
,unshift
concat
,[...arr]
spread syntax (example)pop
,shift
,splice
filter
,slice
(example)splice
,arr[i] = ...
assignmentmap
(example)reverse
,sort
Here is the issue with the the first one. In the first one, you are returning
prev
but that refers to the samearray object
as the previous state + because you reassigned an item inside an array - a refresh will not be triggered.Read more on React docs.
It seems like a waste of memory to be making copies every time - but my understanding now is that in the React rendering process it compares previous virtual DOM to the new virtual DOM to determine whether to update/re-render parts of the screen.
And if the framework team did NOT do this (ie. if they did not generally prefer new copies then they would have to be much more thorough in state comparisons); so for example, in cases where there was a long
array
, they would have to go item-by-item to compare for updates/changes which would be expensive/slow.So my understanding here is that the framework team made a trade-off of more memory usage for the sake of speed.
This answer that starts with:
is correct.
However, for a deeper understanding, it might be worth noting that a similar principle also applies to
objects
and not justarray objects
.The long story short is this.
1/ React does many things under the hood – the most relevant is the reconciliation process between previous and new virtual DOM to figure out which parts of the screen to re-paint
2/ Assuming data immutability when comparing state previous and current state makes React’s updating fast/efficient. Ref here
3/ React uses Javascript under the hood
4/ Javascript has equality comparators and it uses Object.is() to determine if the new state is equivalent to old state. Ref here
5/ Object.is() is not equal to
===
. I mention this because many explainer articles use the two interchangeably. Ref here and hereIf you’d like to do more further reading on immutability, you can do so here and here and here, other ways of comparing objects in Javascript here, and the React doc on updating objects in state is here.