A React Rendering Misconception

React’s diffing algorithm 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.

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:

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.

// global for ease of exampleletrenders=0;// in Axis and Arearender(){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.

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.

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:

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: