Optimizing React Performance - 12 Tools and Tips
Many frontend developers love React because it streamlines the process of performing UI updates, but React apps of substantial size can sometimes become laggy. Sadly, adding a frontend library such as React doesn't instantly improve an app's performance. React must be properly configured.
That said, if you know how to effectively measure and optimize how your app's components render, you can make substantial improvements to how fast your React app, well, reacts. This guide will teach you how to optimize React in addition to providing some JavaScript best practices to deliver more fluid user experiences.
How React works
Since you're reading this, you're probably familiar with React. For those who aren't, React is a popular frontend JavaScript library that lets developers build and compare virtual DOMs, a feature that has become common in other libraries like Vue.js.
All React applications have a root component that branches out in a tree-like formation. These components are actually functions responsible for rendering the UI. After rendering content to the user's browser, React keeps track of user interactions and other requests to virtually re-render the UI. Then, React reconciles the differences between the current UI and the new one using what's called a "diffing" algorithm. That way, it makes only the necessary changes to what the user sees rather than reloading content from scratch.
While this setup avoids expensive DOM manipulation operations that can slow things down, this very process is often at the heart of most performance issues. Adjusting the diffing algorithm allows you to fully reap the benefits of using React.
How to optimize React: Where to begin
Before you make any changes to your app, you must perform some measurements. You'll never know if you're making improvements if you don't know where you started. If you suspect a certain piece of code is slowing everything down, you may be tempted to tackle it right away, but taking advantage of the React performance measuring tools will help you quantify your progress so that you know where to focus your optimization efforts. If you're using a version of React older than version 16 you can install, import and run the react-addons-perf library as follows:
npm install --save-dev react-addons-perf
Import Perf from 'react-addons-perf'
Perf.start();
// your app
Perf.stop();
Perf.printWasted();
You should then see a table like the one below detailing how much time is wasted when components render.
If you're using React version 16.x, it's recommended to take advantage of your browser's profiling tools. Check out this article by React to learn how to use Chrome to profile React components.
To update, or to not update?
React automatically runs and renders an entire virtual DOM by default before checking each and every component for state changes. As the size of your DOM tree expands, this process takes longer and longer. Fortunately, you can let React know which components need re-rendering using the shouldComponentUpdate
method. It works like so:
function shouldComponentUpdate(nextProps, nextState) {
return true;
}
The render-diff process will trigger whenever the above function returns true for a component. If you want to prevent a component from re-rendering, just tell the component to return false. Use the following configuration to compare current and future states in order to determine whether or not a component should be re-rendered:
function shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
Tips to speed up React apps
1. Use React.PureComponents
For components that contain only primitive data, you can perform a "shallow comparison" by making it a React.PureComponent
, which automatically implements a shouldComponentUpdate()
function. Using these as opposed to regular components can speed up React's re-rendering process. Consult the official docs for more information about React.PureComponents
.
2. Implement immutable data structures
If you want your React.PureComponents
to automatically check for complex state changes, then you must set up immutable data structures, which entails making copies of objects with data alterations rather than changing objects directly. Doing so simplifies the process of detecting changes. Toptal has an in-depth tutorial for setting up immutable data structures for JavaScript apps.
3. Take out unnecessary source code
One of React's best features is that it automatically warns developers when part of their code could result in bugs and errors. While extremely helpful, the inclusion of this handy feature can drag down your app's overall performance. Thankfully, there is a way around it.
If you delve into React's source code. you should see the line (process.env.NODE_ENV != 'production'
) repeated throughout. These snippets are responsible for those error warnings. Since they don't benefit your end users, you can delete these instructions in your production environment. A word of caution: Be extra careful when editing React's source code since unintended changes can cause your app to crash.
If you used create-react-app to develop your project, simply run the following to get a production build sans the extra code:
npm run build
If you're working with webpack, just run webpack -p
for the same result.
4. Use constant and inline elements
React Constant Elements treats JSX elements like values, thus moving them to a higher scope and reducing calls to React.createClass
. React Inline Elements achieves the same goal by converting JSX elements into the object literals they are supposed to return.
To set this up, just add the following configuration to your package.json
file:
"babel": {
"env": {
"production": {
"plugins": [
"transform-react-constant-elements",
"transform-react-inline-elements"
]
}
}
},
5. Get chunky
A lot of developers like to bundle their frontend JavaScript code into one minified file, which is fine for smaller React apps; however, as a project grows, the process of delivering the bundled JavaScript file to the user's browser becomes more time-consuming. webpack users can take advantage of the built-in code splitting feature to break their JavaScript into "chunks" that can be delivered to the browser as needed. The webpack docs include a detailed tutorial on code splitting.
6. Use Gzip or Brotli compression
Another way to make your JavaScript files load faster is to enable Gzip or Brotli on your web server. Both Gzip and Brotli compression can drastically reduce the client's data usage thus improving render time. If you're looking for the best compression results, opt to go with Brotli over Gzip.
Furthermore, choose a CDN that supports and caches Brotli-compressed assets.
7. Use ESLint-plugin-React
If you don't already use ESLint for all of your JavaScript projects, now is a good time to start. The ESLint-plugin-React is especially helpful for coding novices since it enforces best practices. Thus, using ESLint on a regular basis could improve your coding skills in the long run.
8. Invoke high order components
The Recompose library includes several high order components that you can call upon to limit unnecessary rendering. For example, use the following setup to make a component re-render only when props change:
@pure
class MyComponent extends Component {
render() {
///...
}
}
Let's say you wanted a component to re-render if prop1 or prop2 changes, but not if prob3 changes. You'd use this setup:
@onlyUpdateForKeys(['prop1', 'prop2'])
class MyComponent extends Component {
render() {
///...
}
}
9. Use connect()
If you're using Redux, you have the higher order component connect()
at your disposal to make things even simpler. Connected components only get re-rendered if designated values are changed. For instances, the following snippet ensures that a component only re-renders when prop1 changes:
connect(state => ({
prop1: state.prop1
}))(SomeComponent)
There is a caveat: If the mapStateToProps
function has to perform a calculation, then it can actually trigger a re-render. For example, the following code would result in an unnecessary re-render:
connect(state => ({
computedData: {
height: state.height,
width: state.width
}
}))(SomeComponent)
To get around this issue, use the reselect function to declare the dependencies to a derived state like so:
import {createSelector} from 'reselect'
const selectComputedData = createSelector(
state => state.height,
state => state.width,
(height, width) => ({
height,
width
})
)
connect(state => ({
computedData: selectComputedData(state)
}))(SomeComponent)
How to optimize React: Tools to speed up React
1. Why did you update
The why-did-you-update
function detects unnecessary component renders. Specifically, it points out instances where a component's render method gets called even though no changes have occurred. You can set it up with a simple npm install:
npm install --save why-did-you-update
Next, include the following snippet in your app code:
import React from 'react'
if (process.env.NODE_ENV !== 'production') {
const {whyDidYouUpdate} = require('why-did-you-update')
whyDidYouUpdate(React)
}
Be sure to disable this feature in your final build so that it doesn't slow things down on the user's end.
2. React developer tools
The React Developer Tools extension for Chrome can highlight component updates to further help you identify unnecessary render cycles. After installing the extension, open it by selecting the React tab in the Chrome DevTools, and then check the "Highlight Updates" box.
As you work on your project, re-rendering components should now be highlighted in blue, green, yellow or red. The cooler colors represent components that update the least frequently while the warmer side of the color spectrum indicates more frequent updates.
Therefore, you should expect to see plenty of yellow and red around UI elements such as sliders. If a single button click causes you to see red, however, then you probably have some unnecessary rendering going on.
3. The Chrome DevTools performance timeline
If you're using React 15.4.0 or later, take advantage of Chrome's performance timeline to pinpoint when your components are mounted, updated and unmounted.
Additionally, you can visualize component lifecycles and compare them to one another. Since this feature depends on the User Timing API, it only works in Chrome, Edge and Internet Explorer. Check the DevTools website for guidance on how to use the performance timeline.
Summary
The key to fine-tuning your React app likely lies in making sure that components only update when they absolutely need to. It should go without saying, but be sure to make performance optimization a regular priority especially whenever you make substantial changes to your app. Don't forget to run benchmarks before and after you make changes to track your progress.