Reputation: 4281
From what I learnt about React, you should not mutate any objects otherwise React doesn't know to re-render, for example, the following example should not trigger re-render in the UI when button is clicked on:
import React, { useState } from "react";
import ReactDOM from "react-dom";
function App({ input }) {
const [items, setItems] = useState(input);
return (
<div>
{items.map((item) => (
<MyItem item={item}/>
))}
<button
onClick={() => {
setItems((prevItems) => {
return prevItems.map((item) => {
if (item.id === 2) {
item.name = Math.random();
}
return item;
});
});
}}
>
Update wouldn't work due to shallow copy
</button>
</div>
);
}
function MyItem ({item}) {
const name = item.name
return <p>{name}</p>
}
ReactDOM.render(
<App
input={[
{ name: "apple", id: 1 },
{ name: "banana", id: 2 }
]}
/>,
document.getElementById("container")
);
You can try above code here
And the correct way to update the array of objects should be like below (other ways of deep copying would work too)
setItems((prevItems) => {
return prevItems.map((item) => {
if (item.id === 2) {
# This way we return a deepcopy of item
return {...item, name: Math.random()}
}
return item;
});
});
Why does the 1st version works fine and the UI is updated right away even though I'm just updating the original item object?
Upvotes: 2
Views: 1404
Reputation: 20754
Render happens due to .map
that creates new array. If you do something like prev[1].name = "x"; return prev;
in your hook, this will not perform an update. Per reactjs doc on setState
with function argument:
If your update function returns the exact same value as the current state, the subsequent rerender will be skipped completely.
Update.
Yes, speaking of parent-child interaction, item
would be the same (by reference), but the child props
would differ. You have MyItem({ item })
and this item
is being destructured from props
, say MyItem(props)
, and this props
changes because of the parent source is changed.
So each time you map
the list, you explicitly ask the parent to render its children, and the fact that some (or all) of the child's param is not changed does not matter. To prove this you may remove any params from the child component:
{items.map(() => ( <MyItem /> ))}
function MyItem () {
return <p>hello</p>
}
MyItem
will be invoked each time you perform items
update via state hook. And its props
will always be different from the previous version.
Upvotes: 2
Reputation: 517
if your setItem setting a new object
, your page with a state change will re-render
in your logic, you performed a shallow copy, which:
Shallow copy and deep copy also created a new object, they both trigger a re-render in React.
The different of shallow copy and deep copy is: from 2nd level of the old object, shallow copy remains the same objects, which deep copy will create new objects in all level.
Upvotes: 0