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.
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
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.
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.
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...
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:
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.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()
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
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
Thou shalt keep a reference to these functions and cancel/clear them on unmount
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
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.
“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
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.
MDN Article: Using Web Workers
MDN Docs: Worker API
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!