---
title: A React Rendering Misconception
teaser: 'In which we learn how often React renders components.

  '
tags: react,performance,web
author: Chris Thorn
published_on: 2017-04-05
---

React's [diffing algorithm][reconciliation] allows developers to author user
interfaces in a declarative way, without worrying about how to handle updates
when the backing data changes. When a component is updated, React only applies
the parts that changed to the DOM. This results in fluid interface transitions,
devoid of flickering.

[reconciliation]: https://facebook.github.io/react/docs/reconciliation.html

When I was learning React, an assumption I made was that a component will only
be re-rendered if something it depends on changes, e.g. a passed in property or
the component's own state is updated. I was surprised to learn this is not true.

Part of the misconception was that I didn't understand that rendering a
component and updating the DOM for that component are two separate steps in the
lifecycle. The component has to be re-rendered in order for the diffing
algorithm to compare it to the previous output. If the output is different, it
will update the DOM accordingly. Re-rendering often isn't necessarily a bad
thing; components are typically small, focused, and cheap. In my case the
component was not cheap, and it was being re-rendered _a lot_.

As an example, let's build a chart with, I dunno, the historical daily use of
emoji and reactions in thoughtbot's Slack. We want some amount of
interaction, so let's draw a vertical line on the chart that matches the mouse
location, and show the values of the data at the location as part of the legend.
Glossing over the details of using [D3] with React, an incomplete example of
such a chart might look like:

[D3]: https://d3js.org/

```jsx
class Chart extends React.Component {
  // ...

  updateMouseLocation(event) {
    this.setState({
      mouseLocation: extractMouseLocation(event),
    });
  }

  render() {
    const { data, mouseLocation, scales } = this.state;

    return (
      <div>
        <svg onMouseMove={e => this.updateMouseLocation(e)}>
          <Axis scale={scales.y} orient="left" />
          <Axis scale={scales.x} orient="bottom" />
          <Area data={data.emoji} scales={scales} />
          <Area data={data.reactions} scales={scales} />
          <MouseLine mouseLocation={mouseLocation} />
        </svg>
        <Legend data={data} scales={scales}
          mouseLocation={mouseLocation} />
      </div>
    );
  }
}
```

Our chart component represents an SVG element, and it delegates the drawing of
all the chart pieces to other components. The `Axis` and `Area` components lean
on D3's capabilities to convert the data into SVG elements. We listen for the
mouse move event on the SVG and update our state with the new position, which is
pushed down to the `MouseLine` and `Legend` components. The `scales` object has
the information about our minimum and maximum values for both axes and the size
of the SVG. It's the workhorse for transforming domain values into coordinates
on the chart.

We'll also include a bit of code in the `Axis` and `Area` components to track
how often they are rendered. We'll use a single counter and each render will
increment it. This happens outside of React's world, to avoid affecting our
experiment.

```js
// global for ease of example
let renders = 0;

// in Axis and Area
render() {
  renders += 1;
  document.getElementById("renders").innerText =
    `renders: ${renders}`;
}
```

When the data is loaded and the chart is initially drawn, we expect the render
count to be `4` - one for each of our two `Axis` and `Area` components. If we
move our mouse over the graph, we'll see a vertical line and some details about
the data at the point.

<iframe width="668px" height="560"
src="https://jsfiddle.net/thorncp/x641f0jo/17/embedded/result/"
frameborder="0"></iframe>

We also see the number of renders skyrocket. Every time the mouse move event
fires for our SVG, the `Chart` component updates its state, which triggers a
re-render of itself and all of its children. The underlying data we're
visualizing isn't changing, so the outputs of the `Axis` and `Area` components
remain the same and their DOM elements are unchanged. However, we are spending
_a ton_ of CPU cycles to determine that. If our chart was complex enough, we
could even see it become sluggish as the CPU can't keep up with the amount of
work we're throwing at it.  We need to fix this, but how?

React's components have a defined [lifecycle]. During an update, the
`shouldComponentUpdate` method will be called _before_ the component is
rendered. If the return value of this method is `false`, our component won't be
rendered.

[lifecycle]: https://facebook.github.io/react/docs/react-component.html#the-component-lifecycle

Now we need to determine when our component _should_ be rendered - if we always
return `false`, we'll never see anything, so we need to be little bit smarter
about it. In our example, `Axis` and `Area` are displaying data they receive
from their properties and the only reason their outputs would change is the
underlying data changes. The `shouldComponentUpdate` method's parameters are
objects representing what the next properties and state _will be_. We _could_ do
a simple comparison to determine if our components should re-render:

```jsx
shouldComponentUpdate(nextProps, nextState) {
  // compare current and next props and state
}
```

However, React has already accounted for our use case. It provides a
`PureComponent` base class that comes with a `shouldComponentUpdate`
implementation that does a shallow comparison of the props and state. Exactly
what we want. We can update our components to take advantage of it:

```jsx
class Axis extends React.PureComponent { ... }
class Area extends React.PureComponent { ... }
```

With those changes in-place, we can see that moving our mouse around the chart
no longer causes our `Axis` and `Area` components to re-render - our count stays
at `4`:

<iframe width="668px" height="560"
src="https://jsfiddle.net/thorncp/6fb74cj0/4/embedded/result/"
frameborder="0"></iframe>

In summary, we've learned about React's [lifecycle] hooks, we've taken advantage
of them to prevent some components from re-rendering, and we've saved precious
CPU cycles on our users' computers.
