Techniques to optimize React render performance: part 2
Tags: webdev, react, tutorial
Published: 2022-03-11 21:11:05

This is the final part in a two part series on optimizing React component render performance in your UI. In [part one of optimizing React performance][part1], we covered tooling, profiling, and generally tracking down exactly where your UI is slow. If you haven't read it yet, check it out. Part 1 was trying to answer _Where is it slow?_ and _Why is it slow?_ Like debugging, knowing exactly where you need to spend your time will make the solution come a lot easier.

By now you should have some UI profiling under your belt and have a good idea of which components are slow. It is high time to fix them. In this post, we will focus on just that: techniques and pitfalls to improve your slow React components.

## Render less

The central tenet of improving performance in general is effectively: "do less work." In React land, that usually translates into rendering less often. One of the initial promises of React and the [virtual DOM][virtualdom] was that you didn't need to think very hard about rendering performance: slowness is caused by updates to the Real DOM, and React abstracts the Real DOM from you in a smart way. [Diffing of the virtual DOM][domdiff] and only updating the necessary elements in the Real DOM will save you.

In UIs with a lot of components, the reality is that you still need to be concerned with how often your components are rendering. The less DOM diffing React needs to do, the faster your UI will be. Do less work, render less often. This will be the focus of our initial performance efforts.

## Example: list of fields

We'll be applying several different optimization techniques to the same example: a list of webform fields. We'll pretend that we've identified this part of the UI as something to optimize. This same example was used in our [first React performance post][part1] and we identified a couple issues:

* When the list re-renders with a lot of fields, it feels slow.
* Each field in the list [renders too often][part1rendertoomuch]; we only want fields that have changed to re-render.

![Example list of webform fields](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n5n7bw6pq8wj775g97z2.png)

A simplified version of the code and a basis for our optimization work:

```js
// Each individual field
const Field = ({ id, label, isActive, onClick }) => (
  <div onClick={onClick} className={isActive ? 'active' : null}>
    {label}
  </div>
)

// Renders all fields
const ListOfFields = ({ fields }) => {
  // Keep track of the active field based on which one
  // was clicked last
  const [activeField, setActiveField] = useState(null)

  return (
    <div>
      {fields.map(({ id, label }) => (
        <Field
          id={id}
          label={label}
          isActive={id === activeField}
          onClick={() => setActiveField(id)}
        />
      ))}
    </div>
  )
}
```
_Our example for techniques in this post_

Note that we are keeping track of an active field in `ListOfFields`. Each time a `Field` is clicked, it will store the last-clicked `Field`'s id in the `ListOfFields` state. The state change will trigger `ListOfFields` to re-render.

By default, when `ListOfFields` re-renders, _all_ of the child `Field` components will re-render as well. For example, clicking one `Field` will set `activeField` state in `ListOfFields` which will cause a `ListOfFields` re-render. The parent re-render will cause all of the child `Field` components to re-render. Every one of them! Every time!

## Solutions

Our potential solutions will center around two main goals:

1. Render child `Field` components less often
2. Compute expensive operations in the render function less often

After this post, you should be able to apply all these techniques to your own codebase while avoiding the pitfalls. Here's what we'll be covering:

* [Pure components](#pure-components)
* [shouldComponentUpdate](#shouldcomponentupdate)
* [Caching computed values][cachingcomputedvalues]
* [Consider your architecture](#consider-your-architecture)
* [Other solutions](#other-potential-solutions)

Let's dig in!

## Pure components

The first potential solution to selective component re-rendering is converting our `Field` component into a [pure component][purecomponent]. A pure component will only re-render if the component's props change. There are caveats, of course, but we'll get to those in a minute.

In our example above, when a `Field` is clicked and the `activeField` state is set, __all__ `Field` components are re-rendered. Not good! The ideal scenario is that only two `Field` components are re-rendered: the previously-active and the newly-active `Field`s. It should skip rendering all of the other `Fields` that did not change.

Pure components are extremely easy to use. Either:

* Wrap a functional component with `React.memo`
* Or define your class component with `React.PureComponent` instead of `React.Component`

```js
import React from 'react'

// These components will only re-render
// when their props change!

// Pure functional component
const Field = React.memo(({ id, label, isActive, onClick }) => (
  <div onClick={onClick}>
    {label}
  </div>
))

// Pure class component
class Field extends React.PureComponent {
  render () {
    const { id, label, isActive, onClick } = this.props
    return (
      <div onClick={onClick}>
        {label}
      </div>
    )
  }
}
```

Using pure components can be an easy win, but it is also very easy to shoot yourself in the foot and unknowingly break re-render prevention.

The big caveat is that a pure component's props are shallow-compared by default. Basically, `if (newProps.label !== oldProps.label) reRender()`. This is fine if all of your props are primitives: strings, numbers, booleans. But things get more complicated if you are passing anything else as props: objects, arrays, or functions.

### Pure component pitfall: callback functions

Here is our original example with `Field` as a pure component. Turns out even in our new example using pure components, the re-rendering issue has not improved&mdash;all `Field` components are still being rendered on each `ListOfFields` render. Why?

```js
// Still re-renders all of the fields :(
const Field = React.memo(({ id, label, isActive, onClick }) => (
  <div onClick={onClick}>
    {label}
  </div>
))

const ListOfFields = ({ fields }) => {
  const [activeField, setActiveField] = useState(null)
  return (
    <div>
      {fields.map(({ id, label }) => (
        <Field
          id={id}
          label={label}
          isActive={id === activeField}
          onClick={() => setActiveField(id)} // Problem!!!
        />
      ))}
    </div>
  )
}
```

The issue is that the `onClick` callback function is being created in the render function. Remember that pure components do a shallow props comparison; they test equality by reference, but two `onClick` functions are not equal between renders: `(() => {}) === (() => {})` is `false`.

How can we fix this? By passing the same function to `onClick` in each re-render. You have a couple options here:

1. Pass in `setActiveField` directly
2. Wrap your callback in the `useCallback` hook
3. Use bound member functions when using class components

Here the issue is fixed with the first two options in a functional component:

```js
const ListOfFields = ({ fields }) => {
  // The useState hook will keep setActiveField the same
  // shallow-equal function between renders
  const [activeField, setActiveField] = useState(null)
  return (
    <div>
      {fields.map(({ id, label }) => (
        <Field
          id={id}
          label={label}
          isActive={id === activeField}

          // Option 1: setActiveField does not change between renders,
          // you can pass it directly without breaking React.memo
          onClick={setActiveField}

          // Option 2: memoize the callback with useCallback
          onClick={useCallback(() => setActiveField(id), [id])}
        />
      ))}
    </div>
  )
}

// An anonymous function in the render method here will not
// trigger additional re-renders
const Field = React.memo(({ id, label, isActive, onClick }) => (
  <div
    // Option 1: Since setActiveField is passed in directly,
    // we need to give it an id. An inline function here is ok
    // and will not trigger re-renders
    onClick={() => onClick(id)}

    // Option 2: Since the id is passed to the setActiveField
    // in the parent component, you can use the callback directly
    onClick={onClick}
  >
    {label}
  </div>
))
```

And a fix using class components:

```js
class Field extends React.PureComponent {
  handleClick = () => {
    const { id, onClick } = this.props
    onClick(id)
  }

  render () {
    const { label, isActive } = this.props
    return (
      <div onClick={this.handleClick}>
        {label}
      </div>
    )
  }
}

class ListOfFields extends React.Component {
  state = { activeField: null }

  // Use a bound function
  handleClick = (activeField) => {
    this.setState({ activeField })
  }

  render () {
    const { fields } = this.props
    return (
      <div>
        {fields.map(({ id, label }) => (
          <Field
            id={id}
            label={label}
            isActive={id === this.state.activeField}

            // Solved! The bound function does not change between renders
            onClick={this.handleClick}
          />
        ))}
      </div>
    )
  }
}
```

### Pure component pitfall: dynamic data in the render function

The function callback pitfall described above is really a subset of a larger issue: passing props dynamically created in the render function. For example, because `{ color: 'blue' }` is defined in the render function here, it will be different on each render, which will force a re-render on every `Field` component.

```js
// Pure component for each individual field
const Field = React.memo(({ label, style }) => (
  <div style={style}>{label}</div>
))

const ListOfFields = ({ fields }) => {
  const style = { color: 'blue' } // Problem!
  return fields.map(({ label }) => (
    <Field
      label={label}
      style={style}
    />
  ))
}
```

The ideal solution is to create the style prop's object somewhere outside of the render function. If you must dynamically create an object or array in the render function, the created object can be wrapped in the `useMemo` hook. The `useMemo` hook is covered in the [caching computed values][cachingcomputedvalues] section below.

## shouldComponentUpdate

By default, pure components shallow-compare props. If you have props that need to be compared in a more complex way, there is a `shouldComponentUpdate` lifecycle function for class components and a functional / hooks equivalent in `React.memo`.

For the functional implementation, `React.memo` takes a second param: a function to do the props comparison. It's still beneficial to shoot for props that do not change between renders unless a re-render is necessary, but the real world is messy and these functions provide an escape hatch.

```js
const Field = React.memo(({ label, style }) => (
  <div style={style}>{label}</div>
), (props, nextProps) => (
  // Return true to NOT re-render
  // We can shallow-compare the label
  props.label === nextProps.label &&
    // But we deep compare the `style` prop
    _.isEqual(props.style, nextProps.style)
))
```

Then implemented as a class component

```js
class Field extends React.Component {
  shouldComponentUpdate () {
    // Return false to NOT re-render
    return props.label !== nextProps.label ||
      // Here we deep compare style
      !_.isEqual(props.style, nextProps.style)
  }

  render () {
    const { label, style } = this.props
    return (
      <div style={style}>{label}</div>
    )
  }
}
```

## Caching computed values

Let's say that while [profiling your app][part1] you've identified an expensive operation happening on each render of `ListOfFields`:

```js
const ListOfFields = ({ fields, filterCriteria }) => {
  const [activeField, setActiveField] = useState(null)

  // This is slow!
  const filteredFields = verySlowFunctionToFilterFields(fields, filterCriteria)

  return filteredFields.map(({ id, label }) => (
    <Field
      id={id}
      label={label}
      isActive={id === activeField}
      onClick={setActiveField}
    />
  ))
}
```

In this example, every time a `Field` is clicked, it will re-run `verySlowFunctionToFilterFields`. But it doesn't need to! The `filteredFields` only need to be computed each time either the `fields` or `filterCriteria` are changed. You can wrap your slow function in the `useMemo()` hook to [memoize](https://en.wikipedia.org/wiki/Memoization) `filteredFields`. Once it's memoized, `verySlowFunctionToFilterFields` will only re-run when `fields` or `filterCriteria` changes.

```js
import React, { useMemo } from 'react'

const ListOfFields = ({ fields, filterCriteria }) => {
  const [activeField, setActiveField] = useState(null)

  // Better, yay
  const filteredFields = useMemo(() => (
    verySlowFunctionToFilterFields(fields, filterCriteria)
  ), [fields, filterCriteria])

  return filteredFields.map(({ id, label }) => (
    <Field
      id={id}
      label={label}
      isActive={id === activeField}
      onClick={setActiveField}
    />
  ))
}
```

Like pure components, you need to be careful that you do not break the comparison. `useMemo` suffers from the same pitfalls as pure components: it performs a shallow-comparison of arguments. That means if `fields` or `filterCriteria` are re-created between renders, it will _still_ re-compute your expensive operation on each render.

Unfortunately `useMemo` does not accept a second comparison argument like `React.memo`. If you want to do a deep comparison there are several [code samples](https://usehooks.com/useMemoCompare/) and [libraries](https://github.com/kentcdodds/use-deep-compare-effect) out there you can use.

### Using `useMemo` to limit re-renders

In our [pure component pitfalls above](#pure-component-pitfall--dynamic-data-in-the-render-function), we noted that passing objects created in the render function can break a pure component's benefits. Note here that the `style` object is being created on each render of `ListOfFields`, forcing all `Field`s to render all the time.

```js
// Pure component for each individual field
const Field = React.memo(({ label, style }) => (
  <div style={style}>{label}</div>
))

const ListOfFields = ({ fields }) => {
  const style = { color: 'blue' } // Problem! Forces Field to always re-render
  return fields.map(({ label }) => (
    <Field
      label={label}
      style={style}
    />
  ))
}
```

While the ideal scenario is to move creation of the `style` object out of the render function, sometimes creating an object in the render function is necessary. In those instances, `useMemo` can be helpful:

```js
const ListOfFields = ({ color, fields }) => {
  // This will be cached until the `color` prop changes
  const style = useMemo(() => ({ color }), [color])
  return fields.map(({ label }) => (
    <Field
      label={label}
      style={style}
    />
  ))
}
```

### Caching computed values in class components

Caching computed values in class components is a bit clunkier, especially if you are trying to avoid the `UNSAFE_componentWillReceiveProps()` lifecycle function. The React maintainers [recommend](https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization) using the [`memoize-one`](https://github.com/alexreardon/memoize-one) library:

```js
import React from 'react'
import memoize from "memoize-one"

class ListOfFields extends React.Component {
  state = { activeField: null }

  handleClick = (id) => this.setState({activeField: id})

  getFilteredFields = memoize(
    (fields, filterCriteria) => (
      verySlowFunctionToFilterFields(fields, filterCriteria)
    )
  )

  render () {
    const { fields, filterCriteria } = this.props
    const filteredFields = this.getFilteredFields(fields, filterCriteria)
    return filteredFields.map(({ id, label }) => (
      <Field
        id={id}
        label={label}
        isActive={id === activeField}
        onClick={this.handleClick}
      />
    ))
  }
}
```

## Consider your architecture

So far, we've focused on pretty tactical solutions: e.g. use this library function in this way. A much broader tool in your toolbox is adjusting your application's architecture to re-render fewer components when things change. At the very least, it is helpful to understand how your app's data flow and data locality affects performance.

A couple questions to answer: at what level are you storing application state? When something changes deep in the component tree, where is the new data stored? Which components are being rendered when state changes?

In the spirit of our webform example, consider the following component tree:

```html
<Application>
  <Navbar />
  <AnExpensiveComponent>
    <ExpensiveChild />
  </AnExpensiveComponent>
  <Webform>
    <ListOfFields>
      <Field />
      <Field />
      <Field />
    </ListOfFields>
  </Webform>
<Application>
```

For the webform editor, we need an array of `fields` stored _somewhere_ in this tree. When a field is clicked or label is updated, the array of `fields` needs to be updated, and some components need to be re-rendered.

Let's say at first we keep the `fields` state in the `<Application />` Component. When a field changes, the newly changed field will bubble up all the way to the `Application` component's state.

```js
const Application = () => {
  const [fields, setFields] = useState([{ id: 'one'}])
  return (
    <>
      <Navbar />
      <AnExpensiveComponent />
      <Webform fields={fields} onChangeFields={setFields} />
    </>
  )
}
```

With this architecture, every field change will cause a re-render of `Application`, which will rightly re-render `Webform` and all the child `Field` components. The downside is that each `Field` change will _also_ trigger a re-render of `Navbar` and `AnExpensiveComponent`. Not ideal! `AnExpensiveComponent` sounds slow! These components do not even care about `fields`, why are they being unnecessarily re-rendered here?

A more performant alternative would be to store the state closer to the components that care about the `fields` array.

```js
const Application = () => (
  <>
    <Navbar />
    <AnExpensiveComponent />
    <Webform />
  </>
)

const Webform = () => {
  const [fields, setFields] = useState([{ id: 'one'}])
  return (
    <ListOfFields fields={fields} onChangeFields={setFields} />
  )
}
```

With this new setup, `Application`, `Navbar`, and `AnExpensiveComponent` are all blissfully unaware of `fields`. Don't render, ain't care.

### In practice: Redux

While I am [not a Redux advocate][isreduxdead], it really shines in this scenario. The [Redux docs][whentouseredux] even outline this as the number one reason to use Redux:

> You have large amounts of application state that are needed in many places in the app.

"Many places in the app" is the key for us here. Redux allows you to `connect()` any component to the Redux store at any level. That way, only the components that need to will re-render when the requisite piece of state changes.

```js
// Application does not need to know about fields
const Application = () => (
  <>
    <Navbar />
    <AnExpensiveComponent />
    <ListOfFields />
  </>
)


// ListOfFieldsComponent does need to know about
// fields and how to update them
const ListOfFieldsComponent = ({ fields, onChangeFields }) => (
  fields.map(({ label, onChangeFields }) => (
    <Field
      label={label}
      style={style}
      onChange={eventuallyCallOnChangeFields}
    />
  ))
)

// This will connect the Redux store only to the component
// where we need the state: ListOfFields
const ListOfFields = connect(
  (state) => ({ fields: state.fields }),
  (dispatch) => {
    onChangeFields: (fields) => dispatch({
      type: 'CHANGE_FIELDS',
      payload: fields
    }),
  }
)(ListOfFieldsComponent)
```

If you're using Redux, it's worth checking which components are being connected to which parts of the store.

### App state best practices?

Deciding where to put your application state, or pieces of your application state is tricky. It depends heavily on what data you are storing, how it needs to be updated, and libraries you're using. In my opinion, there are no hard / fast rules here due to the many tradeoffs.

My philosophy is to initially optimize for consistency and developer reasonability. On many pages, it doesn't matter where the state is, so it makes the most sense to keep the ugly bits in one place. State is where the bugs are, [premature optimization is the root of all evil](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize), so for the sake of our own sanity let's not scatter state around if we can help it.

For example, your company's about page can have all data come into the top-level component. It's fine, and is likely more ideal for developer UX. If performance is an issue for some component, _then_ it's time to think deeper about the performance of your app's state flow and maybe break the paradigm for performance reasons.

At Anvil, we use [Apollo][apollo] to store app state from the API, and mostly adhere to the [Container pattern][containerpattern]: there is a "Container" component at a high level doing the fetching + updating via the API, then "Presentational" component children that consume the data as props. To be a little more concrete:

* Our app's pages all start out with all data for a page being fetched and stored at the `Route` level.
* For complex components with a lot of changes to state, we store state at the deepest level that makes sense.
* We store ephemeral UI state like hover, 'active' elements, modal visibility, etc., as deep as possible.

This is how we approach things, but your organization is likely different. While your approach and philosophical leanings may be different, it's helpful to understand that _the higher the state is in the component tree, the more components React will try to re-render._ Is that an issue? If so, what are the tools to fix it? Those are hard questions. Hopefully the sections above can help give you a bit of direction.

## Other potential solutions

The options covered in the meat of this post can help solve many of your performance ills. But of course they not the end-all to react performance optimization. Here are a couple other quick potential solutions.

### Debouncing

The most important thing to a user is perceived speed. If your app does something slow when they aren't looking, they don't care. [Debouncing](https://css-tricks.com/debouncing-throttling-explained-examples/) is a way to improve perceived speed, i.e. it helps you move some actual work away from a critical part of a user interaction.

A debounced function will ratelimit or group function calls into one function call over some time limit. It's often used to limit events that happen frequently in quick succession, for example keydown events or mousemove events. In those scenarios, instead of doing work on each keystroke or mouse event, it would call your event handler function when a user has stopped typing, or has stopped moving the mouse for some amount of time.

Here's an example using [lodash debounce](https://lodash.com/docs/4.17.15#debounce):

```js
import _ from 'lodash'

function handleKeyDown () {
  console.log('User stopped typing!')
}

// Call handleKeyDown if the user has stopped
// typing for 300 milliseconds
const handleKeyDownDebounced = _.debounce(
  handleKeyDown,
  300
)

<input onKeyDown={handleKeyDownDebounced} />
```

### Rendering very large lists of elements

Do you need to render several hundred or thousands of items in a list? If so, the DOM itself might be the bottleneck. If there are a very large number of elements in the DOM, the browser itself will slow. The technique to solve for this situation is a scrollable list where only the items visible to the user are rendered to the DOM.

You can leverage libraries like [react-virtualized](https://github.com/bvaughn/react-virtualized) or [react-window](https://github.com/bvaughn/react-window) to handle this for you.

## You made it!

Performance optimization is tricky work; it is filled with tradeoffs and could always be better. Hopefully this post helped add tools to your performance optimization toolbox.

Before we depart, I want to stress the importance of [profiling your UI][part1] before applying any of these techniques. You should have a really good idea of which components need to be optimized before digging in. Performance optimization often comes at the expense of readability and almost always adds complexity.

In some cases, blindly adding performance optimizations could actually make your UI slower. For example, it may be tempting to make everything a pure component. Unfortunately that would add overhead. If everything is a pure component, React will be doing unnecessary work comparing props on components that do not need it. Performance work is best applied only to the problem areas. Profile first!

Do you have any feedback? Are you developing something cool with PDFs or paperwork automation? Let us know at developers@useanvil.com. We’d love to hear from you!

[apollo]:https://www.apollographql.com/docs/react/
[purecomponent]:https://reactjs.org/docs/react-api.html#reactpurecomponent
[virtualdom]:https://www.codecademy.com/article/react-virtual-dom
[domdiff]:https://reactjs.org/docs/reconciliation.html
[part1]:/blog/engineering/improve-optimize-react-render-performance-part-1
[part1rendertoomuch]:/blog/engineering/improve-optimize-react-render-performance-part-1#react-profiler
[containerpattern]:https://reactpatterns.com/#container-component
[whentouseredux]:https://redux.js.org/faq/general#when-should-i-use-redux
[isreduxdead]:https://blog.openreplay.com/is-redux-dead
[cachingcomputedvalues]:#caching-computed-values