DEV Community

Cover image for Everything you need to know about React.memo (with examples)
Kairat
Kairat

Posted on

Everything you need to know about React.memo (with examples)

Probably you have already reached some level at React - you have an understanding of what state and props are, how to use basic React hooks - useState, useEffect.

And maybe, you've started to notice that sometimes your React components work very slow (especially the heavy ones with a lot of UI elements and other components)

And you started to ponder on how to fix it and optimize the performance...

Pondering about infinity

After some research, you stumbled upon something called React memo.

You could ask yourself: What the heck is that ?

So, React memo is a HOC - higher-order component that allows you to improve the performance of your React app.
It skips rendering the component if passed props have not changed.

How does it work?

Super simple. A memo will just memoize the rendered output of the component and before the next render, it will compare props.
If nothing changed, the memo will just reuse the last rendered output.

Let me show you an easy example that will demonstrate the difference between the component wrapped into React.memo HOC and just plain component.


We have an "App" component that has 1 state variable "counter".

Also, it has 2 child components - PlainComponent (that is just a plain component that does not use React.memo HOC) and MemoComponent (that is wrapped into React.memo HOC)

function App() {
  const [counter, setCounter] = useState(1);
  return (
    <div className="App">
      <p> {counter}</p>
      <button onClick={() => setCounter(counter + 1)}> Set Counter</button>
      <div className="childComponents">
        <MemoComponent />
        <PlainComponent />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
const PlainComponent = () => {
  console.info("Child Component - no memo HOC");
  return (
    <div>
      <h3> Plain Component </h3>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
const MemoComponent = React.memo(() => {
  console.info("Child Component - uses memo HOC");
  return (
    <div>
      <h3> Memo Component </h3>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

So, when we will change the state of the "App" component (by incrementing a counter), it should cause its child components to re-render.

But as you can see, only the plain component re-rendered.

Example shown - React.memo vs plain component


React.memo & props

But we haven't passed any props to our child components.

What if we gonna pass the prop to the Memo component and this prop will change.

Is it going to ignore this change or it will re-render the component and reflect the modification?

Let's take a look at another example!


We are going to use the same "App" and "MemoComponent" but this time, I added one more state variable to the App component - "passedProp".

This variable will change every time the remainder of our "counter" will be equal to 0.

And we gonna pass this prop to the "MemoComponent"

function App() {
  const [counter, setCounter] = useState(1);
  const [passedProp, setPassedProp] = useState(0);

  useEffect(() => {
    if (counter % 5 === 0) setPassedProp(passedProp + 1);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [counter]);

  return (
    <div className="App">
      <p> {counter}</p>
      <button onClick={() => setCounter(counter + 1)}> Set Counter</button>
      <div className="childComponents">
        <MemoComponent prop={passedProp}/>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In MemoComponent we will just display passed prop

const MemoComponent = React.memo(({prop}) => {
  console.info("Child Component - uses memo HOC");
  return (
    <div>
      <h3> Memo Component </h3>
      <p> {prop}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Every time "passedProp" changes, our MemoComponent re-renders.

Example Shown - props & React.memo


React.memo & state of the component

What if the component wrapped into React.memo HOC has its own state and this state changes?
Is it going to re-render or not?

Now, our MemoComponent has one state variable - "randomNumber" and the button to modify it.

const MemoComponent = React.memo(() => {
  const [randomNumber, setRandomNumber] = useState(Math.random());
  console.info("Child Component - uses memo HOC");

  return (
    <div>
      <h3> Memo Component </h3>
      <p> {randomNumber}</p>
      <button onClick={() => setRandomNumber(Math.random())}>Set random</button>
    </div>
  );
});
Enter fullscreen mode Exit fullscreen mode

Every time we change "randomNumber", our component gonna re-render.

Example shown - React.memo & state of the component

So, if your component has a useState, useContext, or useReducer in its implementation, it will re-render when the state (context) changes.


When to use it ?

  • data-heavy components that are provided the same props all the time

  • big size component that has a decent amount of UI elements

Why not use it everywhere ?

Why not image

Probably you thought about that.

But !!!

Internally React.memo compares props (their previous and new state) so it can decide whether to re-render component or not (if props changed - it should re-render, otherwise not)

And most of the time, computation for this comparison can be even more expensive and take even more time than just re-rendering the component

That is why you should not use React.memo if:

  • component is cheap to re-render
  • passed props change often (so there is no meaning to use memo, the component will re-render anyway)
  • comparison function is expensive to perform

Also, you should not use it as a way to "prevent" a render.
It can lead to bugs!


And the last thing I want to mention is the custom comparison function that can be passed as the second argument.

This function can perform a comparison of previous and new props, and determine whether the component should re-render or not.

Why would we need this?

Consider this example:

In the "App" component we have an object that consists of 1 property and we pass this object to Memo Component.
We do not modify it anywhere.

function App() {
  const [counter, setCounter] = useState(1);
  const complexObject = useState({ qty: 0 });

  return (
    <div className="App">
      <p> {counter}</p>
      <button onClick={() => setCounter(counter + 1)}> Set Counter</button>
      <div className="childComponents">
        <MemoComponent prop={complexObject} />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
const MemoComponent = React.memo(() => {
  console.info("Child Component - uses memo HOC");

  return (
    <div>
      <h3> Memo Component </h3>
    </div>
  );
});
Enter fullscreen mode Exit fullscreen mode

But every time we gonna change the state by incrementing "counter", our MemoComponent is re-rendered (despite the fact we use React.memo)

Why is that happening?

When we change the state of the "App" component, we re-create an object, and React thinks that the passed prop has changed and thus forces MemoComponent to re-render.

Example shown - React.memo & complex objects

So, how to fix it?

Easy-peasy.
We just have to pass the function as a second argument that will compare 2 states of props.

const MemoComponent = React.memo(
  () => {
    console.info("Child Component - uses memo HOC");

    return (
      <div>
        <h3> Memo Component </h3>
      </div>
    );
  },
  (previousProps, nextProps) => {
    return previousProps.prop.qty === nextProps.prop.qty;
  }
);
Enter fullscreen mode Exit fullscreen mode

So, as you can see we check whether the "qty" prop of a passed object has changed or not.

If the state of props is different, we have to return false, and this will cause a component to re-render.

Otherwise, the function returns true and we gonna use the previously rendered output.

Example shown - React.memo & second argument to it


And that's it, guys.
Now you are ready to use React.memo in your React projects!

I hope that you have learned something new today!
I would appreciate it if you could like this post or leave a comment below!
Also, feel free to follow me on GitHub and Medium!

Adios, mi amigos)

Top comments (0)