DEV Community

Manoj Kumar Patra
Manoj Kumar Patra

Posted on

Using React.memo

# Definition

React.memo

  • A higher order component that can be used to get some performance boost if a component renders the same results given the same props
  • Checks for prop changes, and skips rendering the component, and reuse the last rendered result in case previous and current props are the same

function PizzaComponent({ name, price }) {
  /* render using props */
  return (
    <div>Pizza: {name}</div>
    <div>Total: ${price}</div>
  )
}

export default React.memo(PizzaComponent);

Enter fullscreen mode Exit fullscreen mode

This component is a candidate for memoization as given a 🍕 image, it always renders the same 🍕 details.

  • By default, it does a shallow comparison of props
  • For custom control on props comparison, a custom comparison function can be provided as a second argument.

Let's extend our PizzaComponent to also show different toppings available for a 🍕. Toppings will be an array of strings. As mentioned above, React.memo does a shallow comparison of props, so, in this case, even though the toppings array remains same, it will still re-render.

So, how do we fix this?

We pass a custom comparison function as follows:


function PizzaComponent({ name, price, toppings }) {
  function areEqual(prevProps, nextProps) {
    /*
    return true if passing nextProps to render would return
    the same result as passing prevProps to render,
    otherwise return false
    */
    return (
      prevProps.name === nextProps.name,
      prevProps.price === nextProps.price,
      prevProps.toppings.every(topping => nextProps.toppings.includes(topping))
    )
  }

  /* render using props */
  return (
    <div>Pizza: {name}</div>
    <div>Total: ${price}</div>
    {toppings.map((topping, index) => (
      <div key={`${name}_${topping}`}>{topping}</div>
    ))}
  )
}

export default React.memo(PizzaComponent, areEqual);

Enter fullscreen mode Exit fullscreen mode

# Using React memo

Use React.memo to get a performance boost when:

  1. Component re-renders often
  2. Component is usually provided with the same props during re-rendering
  3. Component contains a relevant amount of elements to reason props equality check

If a component with hooks is wrapped with React.memo, then it will still re-render when state or context changes.

# React memo with function as props

Let's add the functionality to order a 🍕 in our PizzaComponent called onOrder as follows:


function PizzaComponent({ name, price, toppings, onOrder }) {
  function areEqual(prevProps, nextProps) {
    /*
    return true if passing nextProps to render would return
    the same result as passing prevProps to render,
    otherwise return false
    */
    return (
      prevProps.name === nextProps.name,
      prevProps.price === nextProps.price,
      prevProps.toppings.every(topping => nextProps.toppings.includes(topping))
    )
  }

  /* render using props */
  return (
    <div>Pizza: {name}</div>
    <div>Total: ${price}</div>
    {toppings.map((topping, index) => (
      <div key={`${name}_${topping}`}>{topping}</div>
    ))}
    <button onClick={onOrder}>Order</button>
  )
}

export default React.memo(PizzaComponent, areEqual);

Enter fullscreen mode Exit fullscreen mode

and let's use it as follows:


function App ({ store }) {
  const { pizza } = store;
  return (
    <PizzaComponent
      name={pizza.name}
      price={pizza.price}
      toppings={pizza.toppings}
      onOrder={() => placeOrder(pizza.price, pizza.coupon)}
    />
  )
}

Enter fullscreen mode Exit fullscreen mode

Since, React.memo does a shallow comparison, this will do a re-render everytime as it will see the callback function onOrder as a new prop everytime.

To fix this, we can wrap the function passed to onOrder with useCallback as follows:


function App ({ store }) {
  const { pizza } = store;
  /**
   * This will always return the same function instance as long as pizza is the same.
   */
  const onOrder = useCallback(
    () => placeOrder(pizza.price, pizza.coupon),
    [pizza],
  );

  return (
    <PizzaComponent
      name={pizza.name}
      price={pizza.price}
      toppings={pizza.toppings}
      onOrder={onOrder}
    />
  )
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)