🍉

How and when to use React.memo

March 07, 2023

We can use React.memo to optimize our app’s performance. When React component re-renders, it also re-renders all the tree of children components. That might be excessive because some of the children could stay the same, and there is no need to trigger an additional render for them. React.memo is our way to say React that it may skip the re-render of the component, if its props didn’t change, and return its cached version instead.

How to use React.memo

Memo serves as a wrapper around our original component. It’s a HOC, so it takes our original component and returns a new, memoized version of it.

const MemoComponent = React.memo(() => {});

If the props of our original component don’t change, React will return the memoized component.

Object.is comparison is used to determine if props have changed. That means shallow comparison. So, if we want to compare values nested inside an object, we can pass custom comparison function as a second argument.

const MemoComponent = React.memo(  
  () => {},  
  (prevProps, nextProps) => {  
    if (prevProps.value === nextProps.value) {  
      return true;  
    }  
    return false;  
  }

Read more about the comparison of values

We need to be extra cautious when using the comparison function, since we need to know the exact shape of props object, as well as ensure we’ve covered all the properties, before coming the conclusion that new props are the same.

Example

Check the example on code sandbox illustrating the difference between standard and memo components.

import { memo, useState } from "react";  
  
// comparison function  
const arePropsEqual = (prevProps, nextProps) => {  
  if (prevProps.value === nextProps.value) {  
    return true;  
  }  
  
  return false;  
};  
  
const MemoComponent = memo(({ value }) => {  
  const [internalValue, setInternalValue] = useState(1);  
  
  console.log("MemoComponent re-rendered");  
  
  const changeInternalValue = () => {  
    // Memo component re-renders when its own state changes,  
    // just like a standard component    
    setInternalValue(internalValue + 1);  
  };  
  
  return (  
    <div style={{ border: "2px solid red" }}>  
      <h1>MemoComponent</h1>  
      <p>MemoComponent props value: {value}</p>  
      <button onClick={changeInternalValue}>  
        Change MemoComponent internal state value  
      </button>  
      <p>MemoComponent internal state value: {internalValue}</p>  
    </div>  );  
}, arePropsEqual);  
  
const StandardComponent = ({ value }) => {  
  console.log("StandartComponent re-rendered");  
  return (  
    <div style={{ border: "2px solid blue" }}>  
      <h1>StandardComponent</h1>  
      <p>StandardComponent props value: {value}</p>  
    </div>  );  
};  
  
function App() {  
  const [memoComponentValue, setMemoComponentValue] = useState(1);  
  const [standardComponentValue, setStandardComponentValue] = useState(1);  
  const [appValue, setAppValue] = useState(1);  
  
  const changeAppValue = () => {  
    // Parent's state change doesn't trigger  
    // child memo component re-render,    
    // because passed props don't change    
    setAppValue(appValue + 1);  
  };  
  
  const changeMemoComponentValue = () => {  
    // Change of props trigger Memo component to re-render  
    setMemoComponentValue(memoComponentValue + 1);  
  };  
  
  const changeStandardComponentValue = () => {  
    setStandardComponentValue(standardComponentValue + 1);  
  };  
  
  return (  
    <div>  
      <button onClick={changeMemoComponentValue}>  
        Change MemoComponent Value  
      </button>  
      <button onClick={changeStandardComponentValue}>  
        Change StandardComponent Value  
      </button>  
      <button onClick={changeAppValue}>Change App Value</button>  
      <p>App value: {appValue}</p>  
      <MemoComponent value={memoComponentValue} />  
      <StandardComponent value={standardComponentValue} />  
    </div>  );  
}  

When to use React.memo

If the output result of render is always the same with the same props, consider using memo. However, the real benefit comes when component has a lot of internal logic, which is expensive to calculate on every re-render. Otherwise, you might not see the difference.

Note that React.memo regulates the component’s behavior only when props from the parent component change. If memoized component has its own state or values coming from other sources, it will still re-render as a standard component. This means the usage of useEffect, useReducer, and useContext.

Also, since passed objects and functions are shallowly compared, memo becomes useless if we define those in the parent component on every render because they will always be different. In this case, we would want to use useCallback or useMemo in parent component, to memoize our props.

Read more on official docs about whether we need to use React.memo all the time, and what best practices we can follow when writing React app, to make most of the memoization unnecessary.

Comments

2022 — 2023 Made by Arbuznik with ♡️ and Gatsby