A component can re-render even if its props don’t change. More often than not this is due to a parent component re-rendering causing the child to re-render.

To avoid this, we can wrap the child component in React.memo() to ensure it only re-renders if props have changed:

function SubComponent({ text }) {
  return (
    <div>
      SubComponent: { text }
    </div>
  );
}
const MemoizedSubComponent = React.memo(SubComponent);

The memoized version of the component above will now compare props and only re-render if they have changed.

For such a small component this may not make a huge difference, but if such a component were rendered thousands of times the performance benefits would quickly start to compound.

It’s worth noting that comparison is done on a shallow basis, so if you need fine-grained control you can supply a custom comparison function as the second argument.

Gotcha #1: Handling Callback Props

Watch out when memorizing components which accept callback props as these callback functions may be be different on each render:

function SubComponent({ handleClick }) {
  return (
    <div onClick={handleClick}>
      SubComponent
    </div>
  );
}
const MemoizedSubComponent = React.memo(SubComponent);


function ParentComponent() {
  return (    
        <MemoizedSubComponent
            handleClick={() => {
                // this function is different on each render!
            }}
        />      
  );
}

In the example above, the anonymous arrow function passed as the handleClick prop will be different on each render. This means that despite being memorized, <SubComponent> will re-render each time <ParentComponent> re-renders because it’s props will have changed.

To avoid this we need to ensure the function passed to <SubComponent> as the handleClick prop is the same function instance between renders. To do this we can wrap the function in useCallback.

function ParentComponent() {
    const onHandleClick = useCallback(() => {
        // this will return the same function 
        // instance between re-renders
    });

    return (    
        <MemoizedSubComponent
            handleClick={onHandleClick}
        />      
    );
}

The official React docs explain this nicely:

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders

https://reactjs.org/docs/hooks-reference.html#usecallback

Gotcha #2: overusing React.memo()

A good rule of thumb when applying React.memo is to ask “does this component re-render often with the same props?”.

If the answer is no, you most likely don’t need React.memo and by using it you’ll be adding unwanted overhead.

If you’re unsure, Dmitri Pavluin has a diagram on his Blog which has a 4-point plan for determining when to use React.memo(). His guideline states that to benefit from React.memo the component should:

  1. Be a Pure functional component.
  2. Render often.
  3. Usually, re-render with the same props.
  4. Be “medium” to “big” in size.

Gotcha #3: state and hooks cause re-renders

It’s worth noting that components wrapped in React.memo will still re-render if their state (useState) or context (useContext) change.

Therefore, even if props stay the same a component may still re-render. Debug those unwanted setState calls!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.