React at Light Speed

Lessons in optimizing performance at Vixlet

Over the past year or so, the web team here at Vixlet has embarked on an exciting journey of moving our entire web application to a React + Redux architecture. It has been a growing opportunity for our entire team and throughout this process, we have some challenges along the way.

Since our web-app can have very large feed views consisting of hundreds if not thousands of media/text/video/link items, we spent considerable time looking for ways to get the most out of React’s performance. Here we will share some of the lessons we have learned along the way.

Disclaimer: The practices and methods described below have been found applicable to the performance needs of our specific application. However, as with all development advice, it’s important to take the needs of your application and team into account. React is pretty fast out of the box, so you may not need as finely-tuned performance as our use case. Nevertheless, we hope you find this post informative.

Fundamentals

Taking a first step into a larger world.

The render() function

As a general principle, do as little work as possible in the render function. If it is necessary to perform complex operations or calculations, perhaps consider moving them to a memoized function so that duplicate results can be cached. See Lodash.memoize for an out-of-the-box memoization function.

Conversely, it is also important to avoid storing easily computable values in a component’s state. For example, if the props contain both a firstName and lastName, there is no need to include fullName in state, since it’s easy to derive from the provided props. If a value can be derived from props in a performant way, by using simple string concatenation, or basic arithmetic operations, there’s no reason for the derived value to be included in a component’s state.

Prop Values and Reconciliation

It is important to remember that React will trigger a re-render anytime a prop (or state) value is not equal to the previous value. This includes any changes within nested values if props or state includes an object or array. With this in mind, it is important to be mindful of situations where it’s possible to inadvertently cause a performance hit by creating new values for props or state each render cycle.

Example: Issues with function binding

Example: Object or Array literals

Example: Be careful of literals in fallback values

Keeping Props (and State) as simple and minimal as possible

Ideally, the only props passed to a component should be those directly needed by that component. Passing a large, complex object, or many individual props to a component only for the purpose of passing values to children will cause many unnecessary component renders (as well as add development complexity).

Here at Vixlet, we use Redux as a state container, so in our case, it is most ideal to use the connect() function from the react-redux library at each level in the component hierarchy to only fetch the direct data needed from the store. The connect() function is highly performant and overhead of using it is very minimal.

Component Methods

Since component methods are created for each instance of a component, if possible, use either pure functions from a helper/util module or static class methods. This makes a noticeable difference especially in cases where there are a large number of a component being rendered in the app.

Advanced

From my point of view jank is evil!

shouldComponentUpdate()

React includes a lifecycle method shouldComponentUpdate(). This method can be used to tell React whether a component should be re-rendered or not depending on the values of the current and next props/state.

One problem with using this method however, is that the developer must be conscious of accounting for every condition in which a re-render must happen. This can get logically complex and in general, be a pain in the ass. You can use a custom shouldComponentUpdate() method if absolutely needed, but for many cases there is a better option...

React.PureComponent

Starting in React v15, the library includes a PureComponent class which can be used to build components. React.PureComponent basically implements it's own shouldComponentUpdate()method that performs a shallow compare between current and next props/state automatically. For more info on shallow comparison see this Stack Overflow:

http://stackoverflow.com/questions/36084515/how-does-shallow-compare-work-in-react

In almost all cases, React.PureComponent is a better choice than React.Component. When creating new components, try building it as a pure component first and only if the component's functionality requires, use React.Component.

More Info: React.PureComponent in the official React Docs

Component Performance Profiling (in Chrome)

In the latest versions of Chrome, there is additional functionality built into the timeline tool that can display detailed information as to which React components are rendering and how long they take. To enable this functionality add ?react_perf as a query string to the url that you want to test. The React rendering timeline data will be under the User Timing section.

More Info: Profiling Components with Chrome Timeline in the official React docs

Useful Utility: why-did-you-update

This is a nifty NPM package that can monkey patch React to add console notifications when a component re-renders unnecessarily.

Note: This module can be initialized with a filter to match the specific component you wish to profile, otherwise your console will be spammed beyond all recognition (SPUBAR?) and possibly your browser will hang/crash. See the why-did-you-update docs for more usage details.

Common Performance Traps

setTimeout() and setInterval()

Use setTimeout() or setInterval() extremely carefully within a React component. There are almost always better alternatives such as 'resize' and 'scroll' events (note: see next section for caveats).

If you do need to use setTimeout() or setInterval(), you must obey the following two commandments

Thou shalt not set extremely short time durations.

Setting a short duration is most likely unneeded. Be cautions of anything less than 100ms. If shorter durations are really needed, perhaps use window.requestAnimationFrame() instead.

Thou shalt keep a reference to these functions and cancel/clear them on unmount

Both setTimeout() and setInterval() return an identifier for the delayed function that can be used to cancel the operation if needed. Since these functions are run at the global scope, they don't care if your component isn't there anymore, and this can lead to errors and/or death.

Note: this is also true of window.requestAnimationFrame()

The easiest solution to this problem is to use the react-timeout NPM package which provides a higher-order component that can be used that will handle the stuff mentioned above automatically. It adds setTimeout/setInterval, etc functions to the wrapped component’s props. (Special thanks to Vixlet developer Carl Pillot for pointing this one out)

If you do not wish to import a dependency and would like to roll your own solution to this problem, something like the following would work well:

If you are using requestAnimationFrame() to run an animation loop, you could use a very similar solution with slight modification:

Non-debounced Rapid-firing Events

Certain common events can fire extremely rapidly, e.g. ‘scroll’, ‘resize’. It is wise to debounce these events, especially if the event handler is performing anything more than extremely basic functionality.

Lodash has the _.debounce method. There is also a standalone debounce package on NPM.

“But I really need immediate response from the scroll/resize/whatever event”

One pattern I have found that can be used to handle these kind of events and respond in a high performance manner, is to start a requestAnimationFrame() watch loop the first time an event is fired. Then, the debounce() function can be used with the trailing option set to true (this means the function only fires after the stream of rapidly firing events has ended) to stop watching the value. See an example below

Intensive CPU task Thread blocking

Certain tasks will always be CPU intensive and thus can cause issues blocking the main rendering thread. Some examples include very complex math calculations, iterating through very large arrays, reading/writing using the File api. Encoding or decoding image data from a <canvas> object.

If at all possible in these scenarios, it would be best to use a Web Worker to move that functionality onto another thread, so that our main rendering thread can remain buttery smooth.

Read These

MDN Article: Using Web Workers

MDN Docs: Worker API

Closing Words

We hope that you have found the above suggestions useful and informative. The above compilation of tips and tricks could not be possible without the great work and research by the web team here at Vixlet. They truly are one of the most awesome groups of people that I’ve ever had the chance to work with.

In your personal quest to get the most out of React, continue to learn and practice, and may the Force be with you!