React Memo vs useMemo

Published on Monday, 11 March 2024

In this article, I am going to share a revised version of what memo and useMemo do. I will answer the question: should you wrap every prop with memo and useMemo? I will discuss when to use and when not to use memo and useMemo, and how to optimize with memo and useMemo. Finally, I will conclude on which is better.

What is Memoization?

Memoization is a performance optimization. It is used to cache return values from expensive calculations. Unnecessary re-rendering can affect the performance of our app. React should not have to go through the render phase only to discard the output. Memoization only lets your component re-render when dependencies aren't the same.

What does memo() do?

While working with React components, you often pass state data as props from the parent to its child components. If the state in the parent component changes, React will re-render the parent component and its child components; it does not matter if the child component was receiving the data as props or not; React will re-render the child components because the parent component re-rendered.

With memo, you can skip re-rending a child component when the props passed to it from the parent component did not change. So, If you change the state in the parent component that has no business with the child component, it doesn't affect the child component, and the child component does not re-render. React will reuse the render output from the previous render.

memo is a higher-order component used for optimising rendering behaviour.

1
2export const ChildComponent = memo(function ChildComponent(){})
3
4or 
5
6const ChildComponent = () => {}
7export const MemoizedChildComponent = memo(ChildComponent)
8

How It Works;

memo takes in two parameters;

  1. Component: the component you want to memorize. React returns a new memoised component.
  2. arePropsequal: This is optional. By default, React uses [object.is](http://object.is) to compare the previous props with the new props. You can use your own custom comparison function here if you don't want to use the default.

Memo does a shallow comparison, and it is not advisable to pass complex data as props. Read more about how object.is comparison work here.

For a detailed code example of how memo works, see skipping re-rendering when props are changed on the React docs.

The example in the code sandbox from the official docs shows you how the Greeting component re-renders only when there is a change in the names props. It does not re-render when the address props change because it is wrapped in memo.

1//  code sample from the link to the official docs
2import { memo, useState } from 'react';
3
4export default function MyApp() {
5  const [name, setName] = useState('');
6  const [address, setAddress] = useState('');
7
8  return (
9	  // ... code implementation
10  );
11}
12
13const Greeting = memo(function Greeting({ name }) {
14
15  console.log("Greeting was rendered at", new Date().toLocaleTimeString());
16
17  return <h3>Hello{name && ', '}{name}!</h3>;
18});
19

When should we use memo?

  • If the component renders often with the same exact props
  • if the components rendering logic is expensive
  • If you are working
  • when your child component is being asked to re-render due to changes in the parent’s state, which do not affect the child's component props anyway.

When should we not use memo

  • If your application mostly consists of static pages where changes are mostly based on navigations or replacing a page with another.
  • If there is no lag in your component or you do not notice any slow UI responses or glitching, then there is no need to use memo
  • If the props passed into the component change frequently
  • memoising everything could make your component less readable

What if the child component doesn't have any props

Use the same element reference technique instead bc React automatically optimises the render for you and prevents you from having to add memo over your code base. Thisis the same thing as passing JSX as children.

Incorrect Optimization with React Memo

Updating a memoised component using state or a context.

A component wrapped with memo will still be re-rendered when its own state changes or when a context it is using changes. The memoization only works on the component when you pass props into the component from the parent component.

If memo provides the optimization by comparing the props, why not wrap every single component with memo()?

A quote from Dan Abromov

Shallow comparisons aren't free. they are 0(prop count). and they only buy something if it bails out. All comparisons where we end up re-rendering are wasted. Why would you expect always comparing to be faster? Considering many components always get different props”

Let’s break this down further in Maths to see how it works:

1normal component render time = 10ms
2shallow comparison = 2ms
3
4- Component is rendered on the screen for the first time, `memo` saves the initial render.
5
6- Initial re-render; `memo` saves the initial render.
7	render = 10ms
8- second re-render; `memo` runs shallow comparison; no prop changed
9	render = 2ms
10- third re-render; `memo` runs shallow comparison; props changed
11	render + shallow comparison = 12ms
12- fourth re-render; `memo` runs shallow comparison; no props changed
13	render = 2ms
14
15If your Component keeps getting different props, your render time will look like this;
16
17- Initial re-render; `memo` saves the initial render.
18	render = 10ms
19- second re-render; `memo` runs shallow comparison; prop changed
20	render + shallow comparison = 12ms
21- third re-render; `memo` runs shallow comparison; prop changed
22	render + shallow comparison = 12m
23- fourth re-render; `memo` runs shallow comparison; prop changed
24	render + shallow comparison = 12m
25- fifth re-render; `memo` runs shallow comparison; prop changed
26	render + shallow comparison = 12m
27
28

You get the point. So, wrapping everything in memo can affect your app's performance. Therefore, only rely on memo as a performance optimization and when you really need to use it or notice the component's render time is large. It also works best for interactions that are granular, like drawing tools; then, you might find memorization helpful.

Most times you have to pair useCallback and useMemo with memo().

useMemo

memo() is a HOC while useMemo is a React Hook that stores the result of a calculation from a pure function so it can be reused without doing the calculation again between updates to the screen(re-renders).

1const cachedValue = useMemo(calculateValue, dependencies)
2

How it Works;

It takes in 2 parameters;

calculateValue: The function calculating the value that you want to cache. The function passed in should be pure, which means it takes no parameters and returns any type.

Dependencies: The list of all reactive values referenced inside the calculateValue code. React will compare each dependency with its previous value using the Object.is comparison.

On the first render, React will return the result of calling calculateValue with no arguments.

On the next re-render, React will check if the dependency values have changed. If they haven't, it will return the previously stored value from the calculation. Otherwise, it will call the calculation again and return the current value from the calculation.

In the example below, we sort through a list and save the value in the filterList variable. This variable is passed as props to the child component profile. By default, React re-renders child components when the parent component re-renders. If there is no lag or nothing slowing your app down, then it's fine. However, if there is, consider wrapping the data mutation or heavy calculation in your component with useMemo.

1function Contacts() {
2  const [text, setText] = useState<string>('');
3  const [list, setList] = useState<[]>([]);
4
5  const filterList = useMemo(
6    () =>
7      list.filter((user) =>
8        user.name.toLowerCase().includes(text.toLowerCase())
9      ),
10    [list, text]
11  );
12
13  return <Profile list={list} text={text} />;
14}
15
16const Profile = memo(function Profile({ list, text }) {
17  // ...
18});
19

For a detailed example of how useMemo works, see the difference between skipping re-renders and always re-rendering code example on the react official docs. Interact with the UI to see how it works.

When should we use useMemo?

  • If you are filtering or transforming a large array or performing an expensive calculation. If the data hasn't changed, you might want to avoid repeating the process.
  • If the calculation you're placing in useMemo is noticeably slow and useMemo's dependency rarely changes.
  • If you're passing it as props to a component wrapped in memo. You can skip re-rendering the component when the values haven't changed.
  • If you are depending on the value from useEffect, or you are passing the value later to another useMemo function.

When should we not use useMemo

  • If your application primarily consists of static pages where changes are largely based on navigation or replacing one page with another.
  • If there is no lag in your component or you do not notice any slow UI responses or glitches, then there is no need to optimise.
  • If the value passed as props will always be new.

Optimization with memo and useMemo

memo is often used together with useMemo to optimize React applications. They are frequently used together with useCallback as well.

Preventing Re-render when Props are Arrays or Objects

For example, in the code below, we see a person prop sent from the parent Info component to the child Profile component. Any time the parent component re-renders, the child component will re-render, even if its props didn't change and the component is wrapped with memo.

1function Info() {
2  const [name, setName] = useState("Kells");
3  const [age, setAge] = useState(50);
4
5  const person = { name, age };
6
7  return <Profile person={person} />;
8}
9
10const Profile = memo(function Profile({ person }) {
11  // ...
12});
13

Why is this happening? it’s because objects are not the same in JavaScript, i.e. {} is not equal to {}. So when it does its comparison object.is({}, {}) returns false .

To fix this, You can use the useMemo hook to prevent the props from being re-created anytime the parent component re-renders.

1function Info() {
2  const [name, setName] = useState("Kells");
3  const [age, setAge] = useState(50);
4
5  const person = useMemo(() => ({ name, age }), [name, age]);
6
7  return <Profile person={person} />;
8}
9
10const Profile = memo(function Profile({ person }) {
11  // ...
12});
13

Now, the person prop will no longer be recreated when the parent component re-renders because useMemo knows the object did not change and informs memo of this as well.

The example also works with arrays because arrays are objects in JavaScript. Using the same example from above, it illustrates how you can use memo and useMemo together to optimize an expensive calculation that returns an array.

1function Contacts() {
2  const [text, setText] = useState<string>("");
3  const [list, setList] = useState<[]>[];
4
5  const filterList = useMemo(
6    () =>
7      list.filter((user) =>
8        user.name.toLowerCase().includes(text.toLowerCase())
9      ),
10    [list, text]
11  );
12
13  return <Profile list={list} text={text} />;
14}
15
16const Profile = memo(function Profile({ list, text }) {
17  // ...
18});
19

Conclusion: Memo and useMemo, which is better?

Memo and useMemo have more in common than differences, actually.

The major difference is that one is a Higher-Order Component, and the other is a React hook.

In real-life working scenarios, they're most likely used together because they complement each other, rather than choosing which one would be better to use.

To recap, if your app isn't slow, the components’ render time is within a reasonable range, and you don't notice any lag while interacting with the UI, you might not need to optimize or use any memoization technique.

References

React Render Tutorial - React memo

React Render Tutorial - Questions on Optimization

react.dev/useMemo

react.dev/skipping-re-rendering-when-props-are-unchanged

Articles written by Kelechi Ogbonna. All rights reserved

Built with NextJs Typescript and TailwindCSS