mg website logo

Improving Performance with React.memo

February 18, 2020

As you write an application in React, preventing extra renders can provide a huge performance improvement.

To understand React.memo we’ll look at the following:

  • What is React.memo?
  • How does React.memo fit into the React update process?
  • An example of memoization.
  • When to use React.memo and when it’s unnecessary.

What is Memoization?

Memoization is a fancy way of saying ‘store work that you’ve already done’.

Here’s a basic implementation:

const cache = {}

const memo = input => {
  // if our cache already has the result stored,
  // return the value without computing it again
  if (cache[input] != null) {
    return cache[input]
  }

  // if we don't have the result, compute it
  // and store the result in the cache for future calls
  const result = computeSlowThing(input)
  cache[input] = result

  return result
}

React supplies a higher order component called React.memo that is a version of memoization for functional components.

It allows you to tell React to skip rendering a component when it’s props don’t change. This can provide a performance benefit in certain scenarios.

It’s most basic usage looks like:

React.memo(MyComponent)

If you’re familiar with React.PureComponent for class components, memo is the equivalent for functional components.

What happens when a components updates?

When a component updates, React renders it and compares that render result with the previous render result. If that result is different, React updates the DOM.

While this comparison may be fast, what if your component is slow to render?

Memo allows us to tell React to only render the component again if the props have changed.

Using React.memo

Let’s take a look at an example without memo. The CurrentTrack component below will display the song title, artist and how much of the song has played so far.

With each second that passes, currentTime will update to reflect the new position in the song.

const CurrentTrack = ({
  title,
  artist,
  albumArtwork,
  duration,
  currentTime,
}) => {
  return (
    <div>
      <div>Track Title: {title}</div>
      <div>Artist: {artist}</div>
      <img src={albumArtwork} alt="album artwork" />
      <div>
        {currentTime} / {duration}
      </div>
    </div>
  )
}

export default CurrentTrack

Since currentTime updates every second, the props will change at least once per second.

Each time props change, React renders the component and compares the results to the previous render. The render results will be different due to the new currentTime and React will update the DOM accordingly.

How can we modify this component to limit what needs to be rendered each time?

We can split out TrackInfo since it is not dependent on changes to currentTime.

const TrackInfo = ({ title, artist }) => {
  return (
    <>
      <div>Track Title: {title}</div>
      <div>Artist: {artist}</div>
    </>
  )
}

const CurrentTrack = ({ title, artist, duration, currentTime }) => {
  return (
    <div>
      <TrackInfo title={title} artist={artist} />
      <div>
        {currentTime} / {duration}
      </div>
    </div>
  )
}

export default CurrentTrack

Now when currentTime changes, React will render CurrentTrack and compare the results to the previous render. Since they are different it will update the DOM, same as above.

So did we benefit from splitting TrackInfo into it’s own component?

Not much. React will still render TrackInfo on each change of currentTime, but since the render result is the same, React won’t update the DOM.

This is where React.memo comes in. Let’s memoize TrackInfo.

const TrackInfo = ({ title, artist }) => {
  return (
    <>
      <div>Track Title: {title}</div>
      <div>Artist: {artist}</div>
    </>
  )
}

const MemoizedTrackInfo = React.memo(TrackInfo)

const CurrentTrack = ({ title, artist, duration, currentTime }) => {
  return (
    <div>
      <MemoizedTrackInfo title={title} artist={artist} />
      <div>
        {currentTime} / {duration}
      </div>
    </div>
  )
}

export default CurrentTrack

Since we wrapped TrackInfo with React.memo, we are telling React to only render TrackInfo if the props have changed.

As long as title and artist remain the same, React will use the last memoized result instead of rendering again.

Benefits of Memoizing in React

When using a memoized component, React can skip the rendering and comparison work that it would ordinarily do.

In the example above, we prevent having to render TrackInfo on most changes to CurrentTrack.

This becomes more beneficial when the memoized component is doing a lot of work to render, such as rendering many subcomponents or performing a lot of computation.

When to use Memo?

So should you use memo on every component?

No.

Memo adds complexity to your code. It’s best to only use memo when there is a measurable performance improvement from it.

A great time to consider using memo is when a component frequently renders with the same props. If a component’s props are often changing, memo won’t provide a benefit.

To better understand where you may benefit from memoization, it’s helpful to profile your application. For more info see:

Memo, Hooks, and State

Memo only applies to changes in props.

It’s safe to use memo on a functional component that implements hooks or updates internal state. A change to state will still cause a rerender, even when the component is wrapped with React.memo.

Custom Props Comparison

By default, memo will only do a shallow comparison of props. This can cause confusion if the props you’re relying on for rendering are nested within an object.

You can provide your own comparison function as the second argument to the memo function to tell React when props have changed.

For example, assume you have a component that takes in a post object.

If the post changes, memo will work as expected since a new object is passed in. But what if only the title changes and the object remains the same? React.memo won’t know that the props have changed and won’t rerender.

A custom comparison function can fix this. For example:

const BlogPost = props => {
  return (
    <div>
      <h1>{props.post.title}</h1>
      {props.post.content}
    </div>
  )
}

const areEqual = (prevProps, nextProps) => {
  if (
    prevProps.post.title === nextProps.post.title &&
    prevProps.post.content === nextProps.post.content
  ) {
    return true
  }

  return false
}

export default React.memo(BlogPost, areEqual)

Conclusion

Hopefully you have a better grasp on how to use memo to prevent extra rendering!

If you have any questions, comments, or corrections please email me at mike@mguida.com.


Written by Mike Guida who enjoys prolonged outdoor adventures and building stuff with software. Follow him on Twitter