DEV Community

therealnrf
therealnrf

Posted on • Edited on • Originally published at blog.bracketsinstitute.com

React.memo() is your friend

React.memo() is one of those things that should be a part of every React developer's arsenal. It gives us the ability to memoize a React component. As with any tool, before we delve into how to use React.memo(), let's understand the problem first.

Why memoize?

Memoization is a general concept which basically means caching the results of some kind of computation for later use. It is an optimization technique which is used quite extensively in the programming world.

An important point to remember is that whenever memoization is used, there must be a criteria which would dictate when the cached results are no longer valid and the computation must be done again.

To understand the problem this solves, consider the following React component:

import { useState, Fragment } from "react";

function App() {
  const [count, setCount] = useState(0);

  function handleDecrement() {
    setCount((oldCount) => --oldCount);
  }

  function handleIncrement() {
    setCount((oldCount) => ++oldCount);
  }

  return (
    <Fragment>
      <p>Count is {count}</p>
      <button onClick={handleDecrement}>-</button>
      <button onClick={handleIncrement}>+</button>
    </Fragment>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

A simple component that keeps a count which can be increased or decreased.

1

Now let's add another component to <App />. To keep it simple, we'll create a <Message /> component that returns some kind of message depending on the msgId that is passed to it as a prop.

function Message(props) {
  let msg = "hello, world";

  if (props.msgId === 1) {
    msg = "hey there!";
  } else if (props.msgId === 2) {
    msg = "hola!";
  }

  return <p>{msg}</p>;
}
Enter fullscreen mode Exit fullscreen mode

We've kept it simple here but imagine this <Message /> component does some heavy computation or perhaps sends a request to an external API in order to get the final message. We'll simulate this situation by adding everyone's favorite console.log() in the mix.

function Message(props) {
  let msg = "hello, world";

  console.log("Just performed some seriously heavy computation");

  if (props.msgId === 1) {
    msg = "hey there!";
  } else if (props.msgId === 2) {
    msg = "hola!";
  }

  return <p>{msg}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Let's update the <App /> component to use <Message />.

import { useState, Fragment } from "react";

function Message(props) {
  let msg = "hello, world";

  console.log("Just performed some seriously heavy computation");

  if (props.msgId === 1) {
    msg = "hey there!";
  } else if (props.msgId === 2) {
    msg = "hola!";
  }

  return <p>{msg}</p>;
}

function App() {
  const [count, setCount] = useState(0);

  function handleDecrement() {
    setCount((oldCount) => --oldCount);
  }

  function handleIncrement() {
    setCount((oldCount) => ++oldCount);
  }

  return (
    <Fragment>
      <Message msgId={1} />
      <p>Count is {count}</p>
      <button onClick={handleDecrement}>-</button>
      <button onClick={handleIncrement}>+</button>
    </Fragment>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In the video below, take special note of the fact that every time count is changed, the heavy computation is done.

2

To understand why the heavy computation is done every time count changes, check out this post: Re-rendering in React

At this point, take a step back and think about how inefficient our UI is at this moment. count does not affect <Message /> in any way but still every time count is updated, the seriously heavy computation is performed. We only want the computation to occur if the msgId changes because a change in msgId should result in a different message.

React.memo() to the rescue

React.memo() is a higher-order component. It accepts a component as its argument and memoizes the result. The memoized result is updated only if the props of the original component are changed.

To use React.memo(), simply pass your component as an argument and save the result. Our <Message /> component will become:

import { useState, Fragment, memo } from "react";

const Message = memo(function (props) {
  let msg = "hello, world";

  console.log("Just performed some seriously heavy computation");

  if (props.msgId === 1) {
    msg = "hey there!";
  } else if (props.msgId === 2) {
    msg = "hola!";
  }

  return <p>{msg}</p>;
});
Enter fullscreen mode Exit fullscreen mode

Note: I've only imported memo() here. If you have React imported, you can use React.memo() instead of just memo().

Now our code looks like this:

import { useState, Fragment, memo } from "react";

const Message = memo(function (props) {
  let msg = "hello, world";

  console.log("Just performed some seriously heavy computation");

  if (props.msgId === 1) {
    msg = "hey there!";
  } else if (props.msgId === 2) {
    msg = "hola!";
  }

  return <p>{msg}</p>;
});

function App() {
  const [count, setCount] = useState(0);

  function handleDecrement() {
    setCount((oldCount) => --oldCount);
  }

  function handleIncrement() {
    setCount((oldCount) => ++oldCount);
  }

  return (
    <Fragment>
      <Message msgId={1} />
      <p>Count is {count}</p>
      <button onClick={handleDecrement}>-</button>
      <button onClick={handleIncrement}>+</button>
    </Fragment>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This time, notice that the computation is done when the application is refreshed but the change in count no longer has that result.

3


πŸ‘‰πŸ» Subscribe to my newsletter: click here

πŸ‘‰πŸ» Follow me on twitter: click here


Top comments (18)

Collapse
 
suobig profile image
Popov Anton

But isn't React.memo making us a disservice, obfuscating our questionable design decisions? Like in this case we can extract all counting logic into a separate Counter component and actually fix the problem instead of sweeping it under React.memo rug.

Collapse
 
therealnrf profile image
therealnrf

In this case sure but this is just an example to keep things simple.

Collapse
 
findream profile image
findream

Thank you, the post is very useful. If we use memo in every child component, will there be any other problem? What do you think about the fact that some developers and articles recommend that use fewer hooks like 'useCallback'、'memo'?

Collapse
 
therealnrf profile image
therealnrf

There shouldn't be any problem per se if every child component is memoized but something like that is generally not recommended. Unless of course you run into a scenario where it makes sense to do it i.e. you have child components doing some heavy lifting but giving the same result.

useCallback and useMemo (which are equivalent with a slight difference in syntax by the way) do the same thing in that they memoize the result of a function call but, being hooks, they're restricted to be used from within a functional component. React.memo() is much more flexible. In fact, it is extremely common to have a separate file for your component and memoize it just before exporting it. Something like this:

export default React.memo(Message);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tarunsoni profile image
Tarun Soni

Here's a great article for when to use useMemo and useCallback - kentcdodds.com/blog/usememo-and-us...

Collapse
 
frontendengineer profile image
Let's Code

great post! small typo on dicate => dictate that can be corrected easily

Collapse
 
therealnrf profile image
therealnrf

Thanks!

Collapse
 
uwemisrael profile image
Uwem

This really helped improve my react native app. Thanks

Collapse
 
therealnrf profile image
therealnrf

Glad to hear that!

Collapse
 
crs1138 profile image
Honza

Thank you, I finally got it! Is React.memo() the same as React.useMemo()? If so, how does React.useMemo() compare to React.useCallback()?

Collapse
 
therealnrf profile image
therealnrf • Edited

React.memo() is different in that it expects a React component and will compare that component's props to decide whether to re-evaluate the component or not. useMemo() and useCallback() are hooks that watch a dependency array to decide whether to re-evaluate a given function or not.

While specific use-cases for useMemo() and useCallback() can be argued, they're more or less equivalent with a slightly different syntax.

Collapse
 
atulbhattsystem32 profile image
Atul Bhatt

Nice one. It was easy to understand.

Collapse
 
therealnrf profile image
therealnrf

Thanks for your feedback!

Collapse
 
craftogrammer profile image
Rahul

Thank you. Easy to understand!

Collapse
 
therealnrf profile image
therealnrf

Thanks!

Collapse
 
rajendrasinh_09 profile image
RAJENDRASINH PARMAR

Thank you for the post. it is very informative and simple to understand.

Collapse
 
therealnrf profile image
therealnrf

Thanks!

Collapse
 
mrsandman101 profile image
A.B.Santhosh

Isn't the output supposed to show "hola" at least once?