Finding React Components to Optimize

Kyle Robertson
Nov 29, 2017 · 5 min read
SOUND FOREST by WILLPOWER STUDIOS (WILLIAM ISMAEL, CC BY-ND 2.0)

In the first part of this series, we reviewed the virtual DOM and some common ways to spoil React’s process of reconciliation, which is the main reason the library can be so fast. In the second part, we took a closer look at optimizing components connected to the Redux store and applied what we learned in part one to those kinds of components.

So far, we’ve concluded that being mindful of references in render is a best practice for every component, and discovered that there are plenty of hooks in a connected Redux component to bypass redundant work altogether.

But how do you know which components in your application are slowing things down, and the kind of benefit these optimization techniques might provide? Depending on the version of React you’re using, there are a few development tools that can narrow your focus.

Tools to Debug Performance

The results may surprise you, especially if you are using Redux Form. It can also be typical for a component to be rendered four or five times initially, because the state will change every time a Redux action is dispatched to load initial data.

If you’re working with a more complex component, performing some expensive calculations that shouldn’t run more than once on the same data, you may already know further optimization is necessary.

What tools you can use to dive deeper into the performance of your application depends on the version of React you’re using. React 15 — which Riipen currently uses — supports the Perf library, while version 16 does not support it and encourages you to use your browser’s built in performance profiler instead.

Your browser’s profiler can be used for earlier versions of React, too, and it’s a powerful tool worth learning. But you might find it more useful with the latest version, which adds support for APIs like User Timing to report more comprehensive information to the browser. See Ben Schwarz’s introduction to using Chrome’s profiler with React 16, or take a look at how Francois Zaninotto used the profiler with React 15.

Perf

if (process.env.NODE_ENV === 'development' && window) {
window.Perf = require('react-addons-perf');
}

Then you can use the tool in your browser’s developer console, just like a profiler:

> Perf.start()… (perform some state-changing user actions in the browser)> Perf.stop()

printWasted

Example output of the printWasted method. The third column shows the amount of wasted time spent rendering components that were ultimately discarded by React’s reconciliation process. The fourth and fifth columns show the number of instances of a component on the page and the number of times that component rendered, respectively.

The amount of wasted time is particularly useful for determining whether or not redundant rendering is a performance bottleneck. Keep in mind that this number might not tell the whole story: time spent in a particularly heavy Redux selectorFactory or mapStateToProps function is not included.

Also watch the number of times a component renders, as this can help you spot offending outliers. In the table above, single components render as few as 16 and as many as 47 times for the same number of state changes. There’s certainly room for improvement here: while the amount of wasted time is not egregious and unlikely to slow down a speedy desktop, users on mobile devices and older computers might not be so lucky.

Why might a component be rendering so frequently? The culprit is likely a selectorFactory or mapStateToProps Redux function on an ancestor component that is passing as props to its child components new references to the unchanging data. In such a case, React is forced to reconcile the full component hierarchy until it reaches a node in the tree that the reconciliation process can recognize as unchanged from the previous render.

Finding the right ancestor component to optimize can entail a bit of trial and error, but this table is a efficient guide. The solution could be as simple as refactoring mapStateToProps and catching redundant references. If that fails, judicious use of a custom shouldComponentUpdate method should solve the problem. Whatever your optimization, printWasted is a great way to verify (in a quantifiable way) the effects your changes.

Just remember to profile the same user actions every time, or your data will be meaningless to compare!

printOperations

Partial output of printOperations, from the same segment of profile data that was used to generate the printWasted table above.

By comparing the actual operations that were done to the report of wasted render calls, you begin to get a feel for the overall architecture of your application through the eyes of React and ask the right questions that lead to worthwhile refactoring. In the screenshots above, for example, we might ask why the MenuItem component is rendering every time the user types a letter in a form input, and examine the component hierarchy above it to see where unnecessary state changes are leaking through.

The prospect of optimizing React components can be daunting at first, not to mention unexpected, given the library’s reputation for being fast. Once you being to think in terms of the component life-cycle and become aware of how reconciliation happens, however, you can start to write components with performance in mind. It’s not premature optimization to do so — as long as you’re not jumping to shouldComponentUpdate—it’s simply a matter of writing JavaScript in the idiom of React.

Happy refactoring, and stay Riipe!

Riipen Engineering

A discussion of Riipen engineering and technology

Riipen Engineering

A discussion of Riipen engineering and technology

Kyle Robertson

Written by

Full-stack consultant at https://typefirst.ca/

Riipen Engineering

A discussion of Riipen engineering and technology