I’m trying to memoize my ItemCard component, so when I call setItems(), only the ItemCard being updated gets rerendered, is this possible?
I have a super simple component like:
const ItemList = () => {
const [items, setItems] = useState([
{ id: 1, count: 5 },
{ id: 2, count: 7 },
{ id: 3, count: 2 },
]);
const updateItemCount = (itemId, add) => {
const updatedItems = items.map((item) => {
const { id, count } = item;
if (id === itemId) {
return { ...item, count: add ? count + 1 : count - 1 };
}
return item;
});
setItems(updatedItems);
};
return (
<div>
{items.map((item) => (
<ItemCard {...item} updateItemCount={updateItemCount} />
))}
</div>
);
};
And ItemCard looks like
import { memo } from 'react';
const ItemCard = memo(({id, count, updateItemCount}) => {
console.log("re-rendered", id)
return (
<button onClick={() => updateItemCount(id, true)}></button>
);
});
The issue is when I log re-renders, they all seem to re-render after the button click
>Solution :
You need to wrap updateItemCount in useCallback, since it’s re-created on each render, and the memo sees a changed prop. In addition, add a key to each item you render in the map.
If we’ll pass the items array to the useCallback dependencies, the function would be again re-created whenever the items changes. To avoid that use an updater function when you call setItems.
const { useState, useCallback, memo } = React;
const ItemList = () => {
const [items, setItems] = useState(() => [
{ id: 1, count: 5 },
{ id: 2, count: 7 },
{ id: 3, count: 2 },
]);
const updateItemCount = useCallback((itemId, add) => {
setItems(items => items.map(item => {
const { id, count } = item;
if (id === itemId) {
return { ...item, count: add ? count + 1 : count - 1 };
}
return item;
}));
}, []);
return (
<div>
{items.map((item) => (
<ItemCard key={item.id} {...item} updateItemCount={updateItemCount} />
))}
</div>
);
};
const ItemCard = memo(({id, count, updateItemCount}) => {
console.log(`id update: ${id} - count: ${count}`);
return (
<button onClick={() => updateItemCount(id, true)}>{count}</button>
);
});
ReactDOM
.createRoot(root)
.render(<ItemList />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="root"></div>