I have a list with a large amount of items inside. In order to improve rendering performance I decided to memoize single items. This is the code I used:
Parent component
const randomList = [];
for (let i = 0; i < 1000; i++) {
const randomInteger = Math.floor(Math.random() * 100);
randomList.push({ id: randomInteger, status: "" });
}
const List = () => {
const [list, setList] = useState(randomList);
const handleClick = (e) => {
const position = e.currentTarget.id;
const newList = list.map((item, idx) =>
`${idx}` === position ? { ...item, status: "red" } : item
);
setList(newList);
};
return (
<div>
{list.map(({ id, status }, idx) => {
return <Item id={id} idx={idx} status={status} onClick={handleClick} />;
})}
</div>
);
};
Item component
export const ChipTermStyled = styled(({ ...props }) => <Chip {...props} />)(
({ status = "" }) => {
return {
backgroundColor: status,
};
}
);
const Item = ({ id, idx, status, onClick }) => {
console.log(`item-${idx}`);
return (
<ChipTermStyled
id={`${idx}`}
key={`${id}-${idx}`}
label={`${id}-${idx}`}
status={status}
onClick={onClick}
/>
);
};
const arePropsEqual = (prevProps, newProps) =>
prevProps.id === newProps.id &&
prevProps.idx === newProps.idx &&
prevProps.status === newProps.status;
export default memo(Item, arePropsEqual);
This should render Item only when id, idx or status change. And it does the job. But! When I update the parent component list, it doesn’t keep the state between item updates. See this:
I check the list before and after on the first click and they appear ok, but on the second click, the list lost all the previous item statuses.
Why can this be happening? What I’m doing wrong?
2
Answers
The problem was with how the state is being updated. For anyone that is facing this issue, instead of updating the state like this:
You have to update it in the previous value of the state:
Your problem is caused by a combination of the memoization, and the way you update the list.
The
handleClick
function is re-created on each render, and contains the updated list:However, your memoization ignores changes to
handleClick
, and all your components, except the last updated hold a reference to stale version ofhandleClick
, which contains the original version of thelist
in which nothing is marked:To solve that problem, and still have memoization on the
Item
component, wraphandleClick
withuseCallback
, and use a function to update the state instead of usinglist
directly:Now all the properties are memoized, and you don’t need
arePropsEqual
anymore, so just wrap your component withmemo
directly:Demo: