Memoize React components

Tony Quetano
4 min readMar 10, 2017

Memoization is a powerful optimization technique that can greatly speed up your application, but did you know that you can use it on React components as well?

What is memoization?

Alright, let’s start with the basics. If we build our functions as pure and deterministic, we have the safety of knowing that based on the same inputs, the same result will occur. A simple example:

const add = (number1, number2) => {
return number1 + number2;
};
add(2, 4);

No matter how many times we call this function, it will always yield 6 as the result. With functions like this, there is no need to re-compute the result every single time, because we can guarantee the result based on the inputs. As such, we can store a local cache map of [inputs] => [result]. This way, if we call add again with the numbers 2 and 4, we do not need to rerun the function, we can just immediately return 6 from cache with confidence.

For this contrived example the benefit is negligible, but for expensive computation this can be a big time-saver. Additionally, there are a slew of great libraries out there that will handle this for you automatically in a very performant way (examples below), so applying it in your application is a snap.

Usage in React

A common implementation technique for those that use memoization is to memoize the data computation, so that times between state changes are as quick as possible. Libraries like reselect do a fantastic job of making state computation and optimization easier to handle.

But what about the render cycle itself? Based on the documentation for React Reconciliation, we know that before React can perform its diff it needs to compute the next possible element in the tree before it can compare its type, props, children, etc. React’s diffing algorithm is quite fast, but there is no reason we can’t help it out even further by reducing the amount of computation that it needs to do prior to performing its that comparison.

For this article we will be using moize, because it is both ridiculously fast and has a convenient way to memoize React components. That said, it can be done with a little effort with any memoization library that serializes parameters.

Let’s memoize a React component

If you have been adopting functional components (available since 0.14.0), as well as the common Presentation vs. Container components methodology, then likely at least some (hopefully a lot) of your components are pure and deterministic functions. An example:

const UserInfo = ({user, onClickUser, additionalDetails}) => {
const {
image,
name,
title
} = user;
return (
<div className="flex-container" onClick={onClickUser}>
<img className="flex-column-auto" src={image}/>
<div className="flex-column-grow">
<h3>{name}</h3>
<div>{title}</div>

{additionalDetails && (
<span className="small">{additionalDetails}</span>
)}
</div>
</div>
);
};

This is not a simple “hello world” component, but its fairly straightforward: we get user information, and based on it we display a container that has their picture, name, and title. Additionally, when we click the container, it will fire the onClickUser function passed. Finally, if additionalDetails are passed, it conditionally shows them below the title.

In this case we have several children, a bunch of different elements, but it is all based on simple props. Easily memoized!

const MemoizedUserInfo = moize.react(UserInfo);

That simple. Internally, the props and context passed are stored and used as the key for updates, so if the same user object + function + details are passed, it will immediately return from cache. Let’s say you were using this component in displaying the logged-in user’s profile, where those values are likely not to change at all. Now all profile-related renders will be super snappy!

Cool! I want to use this everywhere!

I understand the enthusiasm, but be mindful … this does have the potential for excessive memory consumption! Rather than being garbage collected, the computed elements that you memoize will stick around, so this technique is best used when:

  • The input values will change rarely (e.g., a single boolean prop, or a handful of known text values)
  • The component is not reused in a variety of contexts (when the component is garbage collected, the cache will be too)

If you want to use this technique with something that has many possible inputs or is heavily reused you still can, but you need to be mindful of the cache size.

const MemoizedUserInfo = moize.react(UserInfo, {
maxSize: 5
});

This limits the size of the cache to the 5 most recent entries, but the number 5 in the example is arbitrary … use Perf numbers and your best judgment. If you wanted to maximize use of this technique, use a maxSize of 1 , as this prevents any potential memory increases but also optimizes renders when nothing changes.

Another good way to both limit the cache size but also maximize the possible values cached is to make the memoized component specific to its implementation in another component. For example, if you have one component that is used in ten other components, you can have a memoized version in each one:

import moize from 'moize';// this component will export a non-memoized version
import Foo from './components/Foo';
const MemoizedFoo = moize.react(Foo);const Consumer = () => {
<div>
I will memoize the following entry:
<MemoizedFoo/>
</div>
};

UPDATE

moize has updated to version 3, which no longer uses serialization for React, but a shallow-equal comparison of props and context. The package also includes a convenience method to help with the cache limitation, moize.reactSimple.

Any other gotchas?

This technique is only available for functional components, not those that are created with the class declaration. This is because class-based components lack the same deterministic nature.

Hopefully this helps in your React endeavors, but feedback is always welcome!

--

--