Reputation: 641
I have a trivial list of items, which can update themselves. Update of one item triggers a re-render of all items. I provide unique keys for items, I'd expect React will skip the update of unchanged items. Even when App
recreates items
and handleItemUpdate
function.
What is wrong?
The Codepen example: https://codepen.io/enepom/pen/VwKdxZN
Tap on one item prints 3 item renders in a console, not one.
Item component:
const Item = React.memo(({ id, count, onUpdate }) => {
console.log('> ITEM RENDER', id);
const handleClick = () => {
onUpdate(id, count + 1);
};
return (
<li onClick={handleClick}>{id}: {count}</li>
);
});
App component:
const App = () => {
const [items, setItems] = React.useState([]);
React.useEffect(() => {
setItems([
{ id: 'id1', count: 7 },
{ id: 'id2', count: 8 },
{ id: 'id3', count: 9 },
]);
}, []);
const handleItemUpdate = React.useCallback((itemId, count) => {
const itemIndex = items.findIndex(item => item.id === itemId);
if (itemIndex > -1) {
const itemsCopy = items.slice();
itemsCopy[itemIndex].count = count;
setItems(itemsCopy);
}
}, [items, setItems]);
return (
<ul>
{items.map(item => (
<Item key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} />
))}
</ul>
);
};
Upvotes: 0
Views: 877
Reputation: 641
items
in handleItemUpdate
.This code works as expected:
const handleItemUpdate = React.useCallback((itemId, count) => {
setItems(prevItems => prevItems.map(item =>
item.id === itemId
? { ...item, count }
: item
));
}, [setItems]);
Codepen: https://codepen.io/enepom/pen/qBaKYye
Upvotes: 1
Reputation: 171
This is a way to avoid unnecessary renders:
Add equality function to React.memo:
const Item = React.memo(({ id, count, onUpdate,items }) => {
console.log('> ITEM RENDER', id);
const handleClick = () => {
onUpdate(id, count + 1,items);
};
return (
<li onClick={handleClick}>{id}: {count}</li>
);
}, (prev, next) => prev.id===next.id && prev.count===next.count);
const App = () => {
const [items, setItems] = React.useState([]);
React.useEffect(() => {
setItems([
{ id: 'id1', count: 7 },
{ id: 'id2', count: 8 },
{ id: 'id3', count: 9 },
]);
}, []);
const handleItemUpdate = React.useCallback((itemId, count,items) => {
const itemIndex = items.findIndex(item => item.id === itemId);
if (itemIndex > -1) {
const itemsCopy = items.slice();
itemsCopy[itemIndex].count = count;
setItems(itemsCopy);
}
}, []);
return (
<ul>
{items.map(item => (
<Item key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} items={items}/>
))}
</ul>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Upvotes: 0
Reputation: 1239
Here is one way you can do it. React.memo does a shallow comparison, as you are passing an object it might different by reference even though values are the same, you need to implement arePropsEqual with your custom logic.
const Item = ({ id, count, onUpdate }) => {
console.log('> ITEM RENDER', id);
const handleClick = () => {
onUpdate(id, count + 1);
};
return (
<li onClick={handleClick}>{id}: {count}</li>
);
};
const arePropsEqual =(next,prev)=>{
return next.id ===prev.id && next.count ===prev.count
}
const MemoItem = React.memo(Item,arePropsEqual);
const App = () => {
const [items, setItems] = React.useState([]);
React.useEffect(() => {
setItems([
{ id: 'id1', count: 7 },
{ id: 'id2', count: 8 },
{ id: 'id3', count: 9 },
]);
}, []);
const handleItemUpdate = React.useCallback((itemId, count) => {
const itemIndex = items.findIndex(item => item.id === itemId);
if (itemIndex > -1) {
const itemsCopy = items.slice();
itemsCopy[itemIndex].count = count;
setItems(itemsCopy);
}
}, [items, setItems]);
return (
<ul>
{items.map(item => (
<MemoItem key={item.id} id={item.id} count={item.count} onUpdate={handleItemUpdate} />
))}
</ul>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
Upvotes: 0
Reputation: 171
Any time you update the items, the handleItemUpdate
is recalculated becasue the items
is changed , and in the useCallback dependencies array you have :
[items, setItems]
So, each Item
is rendered again as one property (onUpdate) has changed.
Upvotes: 1