4

I'm struggling with s performance issue with my React application. For example, I have a list of cards which you can add a like like facebook. Everything, all list is rerendering once one of the child is updated so here I'm trying to make use of useMemo or React.memo. I thought I could use React.memo for card component but didn't work out. Not sure if I'm missing some important part..

Parent.js

const Parent = () => {
 const postLike= usePostLike()
 const listData = useRecoilValue(getCardList)
 // listData looks like this -> 
 //[{ id:1, name: "Rose", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc }, 
 // { id:2, name: "Helena", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc },
 // { id: 3, name: "Gutsy", avararImg: "url", image: "url", bodyText: "text", liked: false, likedNum: 1, ...etc }]

  const memoizedListData = useMemo(() => {
    return listData.map(data => {
      return data
    })
  }, [listData])

  return (
    <Wrapper>
      {memoizedListData.map(data => {
        return (
          <Child
            key={data.id}
            data={data}
            postLike={postLike}
          />
        )
      })}
    </Wrapper>
  )
}
export default Parent

usePostLike.js

export const usePressLike = () => {
   const toggleIsSending = useSetRecoilState(isSendingLike)
    const setList = useSetRecoilState(getCardList)

  const asyncCurrentData = useRecoilCallback(
    ({ snapshot }) =>
      async () => {
        const data = await snapshot.getPromise(getCardList)
        return data
      }
  )

  const pressLike = useCallback(
    async (id) => {
      toggleIsSending(true)
      const currentList = await asyncCurrentData()

       ...some api calls but ignore now
       const response = await fetch(url, {
        ...blabla
      })

        if (currentList.length !== 0) {
          const newList = currentList.map(list => {
            if (id === list.id) {
              return {
                ...list,
                liked: true,
                likedNum: list.likedNum + 1,
              }
            }
            return list
          })
          setList(newList)
        }           
       toggleIsSending(false)
      }
    },
    [setList, sendYell]
  )

  return pressLike
}

Child.js


const Child = ({
 postLike,
 data 
}) => {

  const { id, name, avatarImg, image, bodyText, likedNum, liked } = data;

  const onClickPostLike = useCallback(() => {
    postLike(id)
  })

  return (
     <Card>
     // This is Material UI component
     <CardHeader
        avatar={<StyledAvatar src={avatarImg}  />}
        title={name}
         subheader={<SomeImage />} 
      />
     <Image drc={image} />
     <div>{bodyText}</div>
     <LikeButton
        onClickPostLike={onClickPostLike}
        liked={liked}
        likedNum={likedNum}
      /> 
     </Card>
  )
}


export default Child

LikeButton.js


const LikeButton = ({ onClickPostLike, like, likedNum }) => {
  const isSending = useRecoilValue(isSendingLike)

  return (
    <Button
      onClick={() => {
        if (isSending) return;
        onClickPostLike()
      }}
    >
      {liked ? <ColoredLikeIcon /> : <UnColoredLikeIcon />}
     <span> {likedNum} </span>
    </Button>
  )
}

export default LikeButton


The main question here is, what is the best way to use Memos when one of the lists is updated. Memorizing the whole list or each child list in the Parent component, or use React.memo in a child component...(But imagine other things could change too if a user edits them. e.g.text, image...) Always I see the Parent component is highlighted with React dev tool.

Ferran Buireu
  • 28,630
  • 6
  • 39
  • 67
gwrik09
  • 121
  • 1
  • 2
  • 9

1 Answers1

3

use React.memo in a child component

You can do this and provide a custom comparator function:


const Child = React.memo(
  ({
    postLike,
    data 
  }) => {...},
  (prevProps, nextProps) => prevProps.data.liked === nextProps.data.liked
);

Your current use of useMemo doesn't do anything. You can use useMemo as a performance optimization when your component has other state updates and you need to compute an expensive value. Say you have a collapsible panel that displays a list:


const [expand, setExpand] = useState(true);
const serverData = useData();
const transformedData = useMemo(() => 
  transformData(serverData),
[serverData]);

return (...);

useMemo makes it so you don't re-transform the serverData every time the user expands/collapses the panel.

Note, this is sort of a contrived example if you are doing the fetching yourself in an effect, but it does apply for some common libraries like React Apollo.

Andrew
  • 63
  • 4
  • Thanks for the help. I thought when a object of the array of `listData` changes, the rest of objects will be remembered by memo so only one Child component with this mutated object will rerender. Probably this is wrong. – gwrik09 Sep 27 '21 at 16:00