DEV Community

Cover image for React useMemo
Lorenzo Zarantonello
Lorenzo Zarantonello

Posted on

React useMemo

useMemo is a React Hook that allows you to memoize the result of a calculation.

Memoization caches the result of a calculation to avoid recalculating it every time it is needed.

Memoization can improve performance by reducing the number of times expensive calculations need to be performed.

React.dev shows an example of useMemo.

import { useMemo } from 'react';

const cachedValue = useMemo(calculateValue, dependencies)
Enter fullscreen mode Exit fullscreen mode

where

  • calculateValue is the function calculating the value to cache. The function should be a pure function and should take no arguments. It can return any type.
  • dependencies is an array of all the values used inside calculateValue.

useMemo In React Lifecycle

First, React calls useMemo during the initial render, on component mount. The value returned from the calculatedValue function is stored in a variable (cachedValue in the example above).

Then, for all the next renders, React has a choice:

  1. If the dependencies have not changed, use the same value.
  2. If any dependency changes, call calculateValue and cache the newly returned value so it can be reused later.

If the component re-renders for other reasons, useMemo ignores the calculateValue function and uses the cached value.

Examples

1. Separate Components & Push State Down

Sometimes, you can avoid memoization altogether by pushing the state down, as suggested by Josh Comeau.

The reasoning goes like this.
If you have two unrelated components, they can manage their own state independently when there is no reason to lift the state up.

In so doing, the two components are independent. In other words, the rendering and re-rendering of ComponentOne doesn't affect ComponentTwo, and vice-versa.

If the heavy computation happens in one component, the re-rendering of the other component doesn't trigger heavy computations again.

In this example on StackBlitz, I am using the Clock component from Josh Comeau's article, and I am fetching data from jsonplaceholder in a second component called Users.

Since Clock and Users are independent of each other, React re-renders the Clock component every second but not the Users component, which contains an expensive HTTP request.

// App.tsx
...

export const App: FC<{ name: string }> = ({ name }) => {
  return (
    <div>
      <h1>Hello {name}!</h1>
      <Clock />
      <Users />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

This solution is also recommended on react.dev: "Prefer local state and don’t lift state up any further than necessary."

However, in some cases, it is not possible to push the state down. But maybe you can lift the content up, as suggested by Dan Abramov in Before you memo().

2. Lift Content Up & Use Children Prop

Here is the example of Dan Abramov, where the ExpensiveTree components shouldn't be re-rendered if possible.

export default function App() {
  let [color, setColor] = useState('red');
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      <p>Hello, world!</p>
      <ExpensiveTree />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

A possible solution is to "split the App component in two. The parts that depend on the color, together with the color state variable itself, have moved into [a new component called] ColorPicker"

// ColorPicker

function ColorPicker({ children }) {
  let [color, setColor] = useState("red");
  return (
    <div style={{ color }}>
      <input value={color} onChange={(e) => setColor(e.target.value)} />
      {children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

"The parts that don’t care about the color stayed in the App component and are passed to ColorPicker as JSX content, also known as the children prop."

So the restructured App component becomes

// App.tsx

...
export default function App() {
  return (
    <ColorPicker>
      <p>Hello, world!</p>
      <ExpensiveTree />
    </ColorPicker>
  );
}
Enter fullscreen mode Exit fullscreen mode

"When the color changes, ColorPicker re-renders. But it still has the same children prop it got from the App last time, so React doesn’t visit that subtree.
And as a result, doesn’t re-render."

This solution is also recommended by react.dev: "When a component visually wraps other components, let it accept JSX as children. This way, when the wrapper component updates its own state, React knows that its children don’t need to re-render."

If these solutions don't work, we have to resort to memoization.

3. Memoize Components

Let's go back to the first example where we pushed the state down.

If the Clock component logic cannot be pushed down, it remains in the App component. React re-renders the App component (and all children) every time a change occurs (every second). You can find this example on this StackBlitz.

// App.tsx
...

// Use useMemo around Users
export const App: FC<{ name: string }> = ({ name }) => {
  const time = useTime();

  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>{format(time, 'hh:mm:ss a')}</p>
      <Users />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

To keep Users from re-rendering and triggering a fetch request every second we can wrap Users around useMemo before exporting it.

// Users.tsx

...
const Users: FC = () => { ... }

export default React.memo(Users);
Enter fullscreen mode Exit fullscreen mode

This is slightly different from the useMemo syntax we saw above.

const cachedValue = useMemo(calculateValue, dependencies)
Enter fullscreen mode Exit fullscreen mode

That is because we are using memo and not useMemo.
The main difference between memo and useMemo is that memo is a higher-order component (HOC) and useMemo is a React Hook.

  • memo is used to memoize a React component, which means that it caches the output of the component and only re-renders it if its props have changed. This can be useful when a component's rendering is expensive, and you want to avoid unnecessary re-renders.

  • useMemo is used to memoize the result of a function inside a component. This can be useful when you need to perform an expensive calculation, and you want to avoid recalculating it every time the component re-renders.

Memo vs useMemo

4. Memoize Components And Change Props

There is no array of dependencies in memo. So what if we need to trigger the expensive operation again?

If we are memoizing a component, the component will only be re-rendered if its props change.

So here is a memoized component with changing props.

First, we added three buttons so you can choose what data you want to fetch:

Once you click a button, the selected item will be saved in the item variable and passed to the Users component

// App.tsx

...
export const App: FC<{ name: string }> = ({ name }) => {
  const [item, setItem] = useState<Items>('albums');
  const time = useTime();

  return (
    <div>
      <h1>Hello {name}!</h1>
      <p>{format(time, 'hh:mm:ss a')}</p>

      <button onClick={() => setItem('users')}>Users</button>
      <button onClick={() => setItem('posts')}>Posts</button>
      <button onClick={() => setItem('albums')}>Albums</button>

      <Users item={item} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Second, we changed the code in Users to use the prop passed in from App.tsx.

The new prop is called item and is of type Items (you can see all types I used on StackBlitz).

The string assigned to item is then used to fetch the correct data from jsonplaceholder. Fetched data is saved in items and used in the template to render a list of items in the Users component (at this point we should rename the component because it can render other things).

// Users.tsx

const Users: FC = ({ item }: { item: Items }) => {
  const [items, setItems] = useState<User[] | Album[] | Post[]>(null);

  useEffect(() => {
    fetch(`https://jsonplaceholder.typicode.com/${item}`)
      .then((response) => response.json())
      .then((data) => setItems(data));
  }, [item]);

  console.log(items);

  return (
    <div>
      {items &&
        items.map((item: User | Album | Post) => (
          <p key={item.id}>
            {item.name} {item.title}
          </p>
        ))}
    </div>
  );
};

export default React.memo(Users);
Enter fullscreen mode Exit fullscreen mode

Note that when you look at the console, you will see two logs every time you select an item. This is because fetching data is an asynchronous operation.
The first log will print the data that is already in items (the old data) and the second log will print the newly fetched data.

This works fine as long as item is not a collection (e.g. not an array or object).
The problem with arrays is that "every time React re-renders, we're producing a brand new array. They're equivalent in terms of value, but not in terms of reference."
In other words, they are using two different spaces in your computer's memory.

So, if item is an array (let's call it itemArray), we can memoize it as follows:

const itemArray = React.useMemo(() => {
  return [
    { id: 1, value: 'users' },
    { id: 2, value: 'posts' },
    { id: 3, value: 'albums' },
  ];
}, []);
Enter fullscreen mode Exit fullscreen mode

This closes the circle by going back to the initial useMemo syntax we started with.

However, I encourage you to read a more comprehensive explanation about Preserved References

Summary

Memoization caches the result of a calculation to avoid recalculating it every time it is needed.

Use the HOC memo to memoize components and useMemo to memoize the result of a function.

Before using memoization, try to push the state down or lift the content up.

  • Push state down (link)
  • Lift Content (link)

In alternative use memoization.

  • Memoize component (link)
  • Memoize component and change props (link)

As a rule of thumb,

  1. Only memoize values that are expensive to calculate.
  2. Don't memoize values that are frequently updated.

Nice Reads

Top comments (5)

Collapse
 
brense profile image
Rense Bakker

Another rule of thumb should be to always memoize complex values like objects and arrays. For example if you return this from a custom hook:

return { firstName,  lastName, isAdmin }
Enter fullscreen mode Exit fullscreen mode

Even if all those values themselves are memoized or primitive values like boolean or string, components consuming the value returned from your hook, will always interpret this value as "changed" even if neither of the values inside the object have changed. The same goes for arrays. This can lead to unexpected behavior, for example when consuming these values in a side effect.

Collapse
 
lorenzojkrl profile image
Lorenzo Zarantonello

Interesting, I didn't think about it but thanks for sharing.
I feel like there would be quite many useMemo though. Isn't that a potential memory/caching issue?

Collapse
 
brense profile image
Rense Bakker

I have seen people claim that useMemo will cause more memory usage, but I've never seen actual benchmarks to prove this and it doesn't really make sense. You're not storing more information (unless you memoize an entire component), you just save the reference to the value, instead of the value itself. The overhead data structure is very minimal.

Collapse
 
oculus42 profile image
Samuel Rouse

Nice content! I really appreciate an article with a lot of good source links!

One small tip: If you add the type after the three backticks your code examples will be colorized to make them easier to read, like `​`​`​tsx.

// Users.tsx

...
const Users: FC = () => { ... }

export default React.memo(Users);
Enter fullscreen mode Exit fullscreen mode

Just don't copy/paste my three-backtick example above...I had to use zero-width spaces to get it to look correct in the comment.

Collapse
 
lorenzojkrl profile image
Lorenzo Zarantonello • Edited

Thanks for the kind words!
And also for your tip! I'll use it immediately:)