React.js: Improve rendering performance with React.memo

Today I want to share a little JavaScript/React.js code with you:
Improve rendering performance with React.memo.

React.memo is a higher order component. It’s similar to React.PureComponent but for function components instead of classes.


If your function component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.


By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.

Source: The documentation

Let’s go with a minified real world example

What you’ll need:

  • node installed
  • yarn (I’ll use it) or npm (also fine)
  • create-react-app
  • A code editor could be helpful (VS Code in use here)

Create the app

Go to the terminal and run:

npx create-react-app memo-demo
cd memo-demo
yarn start

(or “npm start” depending on whether you want to use yarn or npm)

Create some minimal code to work with

Replace the code in App.js with:

import React, { useState } from 'react';
import './App.css';

const App = () => {

  const [values, setValues] = useState({ value1: 'Hello', value2: 'World' });

  const onChangeValue = e => {
    const { name, value } = e.target
    setValues({ ...values, [name]: value })
  };

  return (
    <div className="App">
      {console.log('App rendered')}
      <h1>This is a memo demo</h1>
      <p>Let's say this component displays two values:</p>
      <input type="text" name='value1' value={values.value1} onChange={onChangeValue} />
      <input type="text" name='value2' value={values.value2} onChange={onChangeValue} />
    </div>
  );
}

export default App;

This will display like this:

Memo demo base app

And when you open the developer tools you’ll see that it always re-renders the app when you are typing something into the input fields. That’s normal.

Memo demo app: Updates on every input

But now add a component to it that is expensive to render and gets its props passed down from here:

import React, { useState } from 'react';
import './App.css';


const ExpensiveComponent = props => {
  console.log('rendered ExpensiveComponent!')
  return (
    <div>
      <h2>and a very expensive to render component that is only interested in value2!</h2>
      <p>{props.values.value2}</p>
    </div>);
};


const App = () => {

  const [values, setValues] = useState({ value1: 'Hello', value2: 'World' });

  const onChangeValue = e => {
    const { name, value } = e.target
    setValues({ ...values, [name]: value })
  };

  return (
    <div className="App">
      {console.log('App rendered')}
      <h1>This is a memo demo</h1>
      <p>Let's say this component displays two values:</p>
      <input type="text" name='value1' value={values.value1} onChange={onChangeValue} />
      <input type="text" name='value2' value={values.value2} onChange={onChangeValue} />
      <ExpensiveComponent values={values} />
    </div>
  );
}

export default App;

OK, this is not really expensive. But I’m too lazy to write a tutorial using a really expensive component. So let’s pretend it is something like a big form or a large table (with infinite scrolling).

It will look like that:

Edit the text fields.
Notice: It doesn’t matter whether you touch value1 or value2: The ExpensiveComponent re-renders.

Let’s change this.

With React.memo

import React, { useState } from 'react';
import './App.css';


const MemoizedExpensiveComponent = React.memo(
  props => {
    console.log('rendered MemoizedExpensiveComponent!')
    return (
      <div>
        <h2>and a very expensive to render component that is only interested in value2!</h2>
        <p>{props.values.value2}</p>
      </div>
    );
  },
  (prevProps, nextProps) => prevProps.values.value2 === nextProps.values.value2
)

const App = () => {

  const [values, setValues] = useState({ value1: 'Hello', value2: 'World' });

  const onChangeValue = e => {
    const { name, value } = e.target
    setValues({ ...values, [name]: value })
  };

  return (
    <div className="App">
      {console.log('App rendered')}
      <h1>This is a memo demo</h1>
      <p>Let's say this component displays two values:</p>
      <input type="text" name='value1' value={values.value1} onChange={onChangeValue} />
      <input type="text" name='value2' value={values.value2} onChange={onChangeValue} />
      <MemoizedExpensiveComponent values={values} />
    </div>
  );
}

export default App;

What did we do here?

We wrapped the ExpensiveComponent with the React.memo higher order component.

However this alone would not have been sufficient, because it causes the component to re-render when the props change. And since we pass down the whole “values” it would happen when value1 changes too.

To help with this the second parameter of React.memo is helpful: You can pass an own compare function. And that’s what we did here: We care only for value2.

As long as this function return true, it will return the memoized component (that is: Not re-render it).

What will happen if we edit the value1 input field now?

React memo demo app reacts only to value2 now

Nothing.

Nice, isn’t it?

One thing you might ask, is why I pass the whole values object here to the MemoizedExpensiveComponent, when value2 would have been sufficient?

Well, that’s a good question actually.

The thing is in real world applications it’s often more difficult: Imagine a search form with a (filterable) result table (with inifite scrolling, meaning it loads data from the backend when you scroll to the end of the viewport). You might need to pass all the props down there. But you are only interested in a few when it comes to update certain parts…

That’s why I preferred to show it like this, with the custom compare function.

You can achieve the same with useMemo hooks and, in class components, some hacks and tweaks in the shouldComponentUpdate lifecycle method.

Happy hacking and feel free to discuss in the comments section!