How to Memoize Components in React

Using React.memo, useMemo other APIs to limit re-rendering of components

Ross Bulat
Oct 22 · 10 min read

Memoizing: Your first port of call for performance optimisation

Memoizing in React is a performance feature of the framework that aims to speed up the render process of components. The technique is used in a wide spectrum of disciplines, from game engines to web applications. This article explores memoization techniques within React specifically, covering the APIs and example use cases along the way.

Memoizing is a well known concept in computer programming, aiming to speed up programs by caching results of expensive function calls and re-using those cached results as to avoid repeating those expensive operations:

Memoizing utilises a system’s memory to store results of expensive operations for subsequent usage.

While Memoizing often-times saves processing cycles, there is a limit to how much it can be used — this is of course dictated by a system’s memory limits. When it comes to React, we are caching the result of a component’s render() method — or simply the returned JSX of a functional component.

Memoizing can be applied to both class components and functional components; the feature is implemented has HOCs and React Hooks — both of which we’ll explore further down.

It is wise to consider how much of your application will be cached via memoizing. Even though I personally have not ran into memory limitations, mobile devices will inevitably have less memory to utilise than laptop or desktop counterparts.

Memoizing in React is not a guarantee that your components will be cached, but rather a best-effort attempt based on factors such as available resources.

Memoizing can help keep UI responsive

There is no denying that fast and responsive UIs are great for the end user, and great for brand recognition with the experience delivered. A UI response delayed by over 100 milliseconds will already become perceptible to the end user. Aiming for 100 milliseconds or less for component re-renders, and UI feedback in general, is the ideal timeframe to keep the app feeling fluid.

Memoizing is just one technique to ensure this continues to be the case. Let’s explore further how Memoizing is implemented in React before looking at example use cases.


Introducing Memoizing with React.memo

Memoizing in React is primarily used for increasing rendering speed while decreasing rendering operations, caching a component’s render() result upon an initial render cycle, and re-using it given the same inputs (props, state, class properties, function variables).

To save these render() operations from repeating and generating an identical result, we can instead cache the result of the initial render() and refer to that result in memory the next time a component renders.

You may be thinking, React.PureComponent does this! React.PureComponent is indeed a performance optimisation, that implements the componentShouldUdpdate() lifecycle method to compare shallow props and state comparison from the previous render. If these match, the component will not re-render.

The term “shallow” is used to denote the props and state of the component being tested only. The props and state of child components are not tested with React.PureComponent.

React.PureComponent is limited to class components only, with its reliance on lifecycle methods and state. To remedy this, React introduced the React.memo API — a higher order component that implements the same shallow comparison on the component’s props to determine if a re-render will be processed. This HOC can then wrap functional components, too.

We can either wrap the API directly around the component:

Or declare the component and React.memo separately:

React.memo also gives us the option to provide our own comparison function as a second argument to the API, giving us more granularity on determining whether a refresh is needed:

It is common practice to refer to a component wrapped in a HOC as WrappedComponent. The memoized component is defined separately and exported as the default export.

Here are a couple of scenarios to consider on whether to implement a comparison function:

  • Perhaps we do not need every prop value to match with the previous render props. For example, if some props are passed down to child components for initialisation, these will not need to be compared on subsequent renders
  • In Reactive applications (Reactive design paradigm, not to be confused with the React framework) you may be listening to external services such as web socket events or an RxJS feed, and only wish to re-render a component when certain data is fetched from a server. Essentially, we can refer to global variables within myComparison so external factors can determine whether the component refreshes or not
  • If you’ve ran into a UI bug, it is simple to just return false from myComparison to temporarily override the memoization, forcing a refresh on every re-render and returning to the default component behaviour

Check out the following demo to see React.memo in action. We are randomly selecting an array of names and passing it down to a NameShuffling component.

The demo is available to clone on Github inside the <App /> component. The following screencast illustrates what is happening within this component,NameShuffling only re-rendering when the name prop changes:

Within the App component, we assign a random name to state via a getName() function:

The name value in state is passed into the NameShuffling component. To update this value, the Shuffle button calls getName() and sets the name to App’s state.

NameShuffling is the component we are memoizing here. It is a functional component wrapped in React.memo:

We have imported memo from React here at the import statement, but we could have also just used React.memo. This is generally the developer’s decision to make.

We can demonstrate the comparison function here too, checking if the name prop is different from the previous render. We can also refer to global variables and external processes:

Here we are further limiting re-renders to when someGlobalVar returns a ready value of 1 and if the name prop has changed. If someGlobalVar was a server response, then the server would have control of when the component can re-render — useful for when the component is waiting for a complete list of data to be fetched before displaying all the results.

Passing functions as props to memoized components

Memoizing components works well with functions as props too, provided that there are no prop or state dependencies in that function. We will cover how to deal with callback functions as props (state included) further down with the useCallback() Hook.

In the demo, a function is passed down to NameShuffling that clears the current name being selected. This will then trigger a re-render as the name will be changed:

NameShuffling itself includes a Clear button, and either displays the currently selected name, or “None” if there is no name selected:

As our component is Memoized, repeatedly clicking Clear will not cause more re-renders after the name is set to null.

This concludes are first basic demo. Let’s now dive into a more complex scenario where multiple memoization methods are used together.


The useMemo and useCallback Hooks

Memoization can also be done with Hooks and functional components, with a more flexible API than the React.memo counterpart that was built around components and props.

To be more specific, we have the ability to wrap inline JSX from a component’s return statement with useMemo, as well as storing Memoized results as variables. We can also memoize callback functions using the useCallback hook, an equivalent of useMemo with slightly different syntax.

useCallback solves the function-as-a-prop issue mentioned earlier, whereby a function is redefined upon a re-render if it is not memoized — we’ll check out a solution in the demo to follow.

The signature of useMemo demonstrates that it takes a function as its first argument, and an array of values — or dependencies — that trigger a re-render if their values change:

useCallback is used in conjunction with useMemo to memoize inline functions, and has a similar signature:

The difference between the two, as React documentation puts it, is that useMemo is used with “creation functions”, whereas useCallback is used for “inline functions”. This is not entirely clear on a first glance, so let’s dive back into the Name Shuffling demo and refactor our code, implementing these hooks in conjunction with React.memo.

The following solution is implemented within AppFunctional.js on Github, and is the default component that renders when running the project.

Name Shuffling demo with functional components

Let’s turn our attention back to the Name Shuffling example, this time implemented within a functional component.

  • useMemo will be used in a similar fashion to React.memo. Instead of wrapping a NameShuffling component with React.memo as we did previously, we will wrap the name display inline JSX with useMemo directly
  • useCallback will be used to memoize our getName() and clearName() methods. getName() will use the names array as its only dependency, only updating when names are added or removed from the list. clearName() will not have any dependencies, and will therefore be initialised and memoized until the component unmounts
  • We’ll wrap <Button /> components with React.memo to ensure they are not re-rendered. Our useCallback functions will be passed into these buttons
  • We will separate the names array into a top level component, and have our useCallback and useMemo hooks defined in a child component, separating the top level data to component logic

The following illustration breaks down this component structure:

Let’s look at some key points here that get Memoizing working smoothly.

1. We are passing our callback functions as useCallback objects

If we simply passed getName() and clearName() functions as props, they would have been recognised as different functions upon re-renders of the parent component — we are in essence redefining those functions upon a re-render of the component — which we do not want to do.

The useCallback hook solves this issue — the getName() function now only updates if names changes, being its only dependency. This makes sense, as names.length may be different, and the names array could contain different set of values. Beyond this scenario getName() will always stay the same — great use case for useCallback.

clearName() is also memoized, but has no dependencies to trigger an update to it. This is perfectly valid.

2. We’ve used useMemo with inline JSX

In this case, embedding useMemo() within the <Shuffle /> component’s return statement works well — we can clearly see the components we wish to memoize:

We have opted to memoize the above <h2> element, and only re-render it if the current name changes.

Wrapping your JSX in <> and </>is the equivalent of using React.Fragment, allowing you to wrap JSX where there are more than 1 top level components. No markup is generated from fragments.

3. We’ve used React.memo for the buttons to prevent them re-rendering

React.memo has been used again for the <Button /> components to perform a shallow prop comparison to determine whether they should be re-rendered. Because we are passing in our memoized callback functions (along with a primitive label string), there should not be any reason to re-render the buttons — the callbacks and labels will always stay the same.

The <Button /> component is simply a wrapped <WrappedButton /> component courtesy of the React.memo HOC. This is how it has been implemented:

The console.log statements are for debugging purposes to make it obvious in the console whether a re-render is taking place.

The full implementation can be found here on Github.


Memoizing Considerations

To use these APIs effectively, consider the following points to ensure your apps remain bug free while leveraging the performance gains of Memoizing:

  • Side effects (anything that affects something outside the scope of the function being executed) should not be performed in a memoized function. Leave this to the commit phase of the component, after its render phase has completed execution
  • Memoizing is performed in the render phase of a component, and side effects should typically be used in componentDidMount, componentDidUpdate, and componentDidCatch with class components, or useEffect with functional components
  • Use the Profiler to record the performance of your app before and after Memoizing. If the performance gains are not considerable, it may be worth removing the added complexity Memoization brings in certain areas of your app where there is not a huge performance gain, freeing up more memory and decreasing boilerplate
  • Compartmentalise a component into child components to leverage memoization if you need to, such as in forms whereby static components such as buttons, labels and icons may be combined with dynamic components that need re-rendering, such as with validation. Separate these and memoize where possible — this is especially useful with complex SVG icons that contain a lot of markup

This has been an introduction to Memoizing in React, using some of the available APIs designed to speed up re-rendering performance of your apps. The latter demo in-particular is a good reference point into using the talked about methods of memoizing — enjoy increasing the speed and fluidity of your apps!

Ross Bulat

Written by

Author and programmer. Director @ JKRBInvestments.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade