reactjsreact-hooksmemoreact-memo

Why memoized items are not persisted in parent components?


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:

enter image description here

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?


Solution

  • The problem was with how the state is being updated. For anyone that is facing this issue, instead of updating the state like this:

     const handleClick = (e) => {
        const position = e.currentTarget.id;
        const newList = list.map((item, idx) =>
         `${idx}` === position ? { ...item, status: "red" } : item
        );
        setList(newList);
     };
    

    You have to update it in the previous value of the state:

     const handleClick = (e) => {
     const position = e.currentTarget.id;
     setList(prevList => 
        prevList.map((item, idx) =>
          ${`idx`} === position 
             ? { ...item, status: "red" } 
             : item
        ));
     };