practices to boost react performance

Practices to Boost React Performance

1. React under the hood?

React DOM wasn’t created with dynamic UI in mind. When the DOM needs to be updated, it has to go through a Reflow/Layout stage and the DOM nodes need to be repainted, which is slow. When you have an application with thousands of

s, there is a severe performance issue.

React uses a different strategy to update your DOM. The process is popularly known as Reconciliation. When your browser initially renders your application, React builds a tree of elements. Let’s imagine that this how React builds its tree.

But what when a component’s state changes, the render() method returns a different tree of React elements. React has to calculate the changes it has to make to the DOM, and it does so by creating a virtual DOM and then comparing that to the real DOM.

The good thing about virtual DOM is that it updates only the part of the DOM that needs to be updated. React uses a diffing algorithm that tries to minimize the number of operations to transform the tree. However, the diffing algorithm isn’t perfect. Let’s say that values of the nodes in yellow has changed, and needs to be updated.

However, the diffing algorithm isn’t perfect. If the updated props were passed in from the com React ends up updating entire subtree like this.

As you can see, React is unnecessarily rendering and diffing component subtrees. Instead, there are ways that you can re-render only the nodes that are directly affected by the props change and not the entire subtree.

When React wastes resources and CPU on rendering components that haven’t changed, we call it wasted renders. Wasted renders can throttle performance — but if we could streamline the diffing process further, we might be able to boost the speed significantly. The rest of the post will cover how to boost React performance.

Benchmark your Application to Improve Performance — Tools and Techniques

React used to have a tool called react-addon-perf that was recently retired and it isn’t available in the latest version of React. Instead, React recommends using the browTo create a performance profile for your application, follow the steps below:

Make sure that you’ve exported the JS with source-maps or that you’re in development mode.
Temporarily disable all React extensions, like React DevTools, because they can impact the results.
Set up the profiling tool. Open the Chrome developer tools and go to the performance tab. You can slow down the JavaScript execution so that the performance issues become more apparent.
Now, navigate to the page that you would like to profile and then press the red button that says “Record.” If you would like to create a performance trace of the initial render, you should choose the start profiling and reload button. Now, perform the desired action and stop the recording.
Chrome will return visualization of the performance data in vertical bands. Zoom in onto the User Timing band to see each component’s performance.
You can select individual components and the bottom up tab will give you insights into activities that take up most of the time.ser’s profiling tools to get the same result.

To create a performance profile for your application, follow the steps below:

Make sure that you’ve exported the JS with source-maps or that you’re in development mode.
Temporarily disable all React extensions, like React DevTools, because they can impact the results.
Set up the profiling tool. Open the Chrome developer tools and go to the performance tab. You can slow down the JavaScript execution so that the performance issues become more apparent.
Now, navigate to the page that you would like to profile and then press the red button that says “Record.” If you would like to create a performance trace of the initial render, you should choose the start profiling and reload button. Now, perform the desired action and stop the recording.
Chrome will return visualization of the performance data in vertical bands. Zoom in onto the User Timing band to see each component’s performance.
You can select individual components and the bottom up tab will give you insights into activities that take up most of the time.

a library, known as why-did-you-update, that is an excellent tool for component analysis. It gives you a visualization of the props before and after and notifies you about components that shouldn’t have been rerendered.To install the tool, run

1
npm install --save why-did-you-update

Add this code into your index.js file.

1
2
3
4
5
6
import React from 'react';

if (process.env.NODE_ENV !== 'production') {
const {whyDidYouUpdate} = require('why-did-you-update');
whyDidYouUpdate(React);
}

2. Prevent Unnecessary Rendering

React re-renders a component when its props or state gets updated:default behaviors.

A. shouldComponentUpdate(). This lifecycle hook doesn’t get invoked during the initial render, but only on subsequent re-renders. shouldComponentUpdate() is triggered when the props have changed and it returns true by default.

If you’re sure that your component doesn’t need to re-render regardless of whether the props have updated or not, you can return false to skip the rendering process.

1
2
3
4
5
6
7
8
9
class ListItem extends Component {
shouldComponentUpdate(nextProps, nextState) {
return false
}

render() {
// Let the new props flow, I am not rendering again.
}
}

Alternatively, if you need to update only when certain props are updated, you can do something like this:

1
2
3
4
5
6
class ListItem extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.isFavourite != this.props.isFavourite;
}
...
}

Pure Component

A React.PureComponent is pretty much like a normal Component but the key difference being, it handles the shouldComponentUpdate() method for you. When the props or the state update, PureComponent will do a shallow comparison on them and update the state.

So, what is shallow comparison? Here’s excerpt from an answer on StackOverflow:

1
Shallow compare does check for equality. When comparing scalar values (numbers, strings) it compares their values. When comparing objects, it does not compare their's attributes - only their references are compared (e.g. "do they point to same object?).

reference: immutable.js is coming!!! //topics involved here.

Personally, you should use PureComponent only if you have a clear understanding of how immutable data works and when to use forceUpdate() to update your component.

3. Debouncing input handlers

This concept is not specific to React, or any other front-end library. Debouncing has long been used in JavaScript to successfully run expensive tasks without hindering performance.

A debounce function can be used to delay certain events so that it doesn’t get fired up every millisecond. This helps you limit the number of API calls, DOM updates, and time consuming tasks. For instance, when you’re typing something into the search menu, there is a short delay before all of the suggestions pop up. The debounce function limits the calls to onChange event handler. In many cases, the API request is usually made after the user has stopped typing.

Let me focus more on the debouncing strategy for input handlers and DOM updates. Input handlers are a potential cause of performance issues. Let me explain how:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default class SearchBar extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onChange(e.target.value);
}
render () {
return (
<label> Search </label>
<input onChange={this.onChange}/>
);
}
}

On every input change, we’re calling this.props.onChange(). What if there is a long-running sequence of actions inside that callback? That’s where debounce comes it handy. You can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {debounce} from 'throttle-debounce';

export default class SearchBarWithDebounce extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
this.onChangeDebounce = debounce( 300,
value => this.props.onChange(value)
);
}
onChange(e) {
this.onChangeDebounce(e.target.value);
}
render () {
return (
<input onChange={this.onChange}/>
);
}
}

The DOM update happens after the user has finished typing, which is exactly what we need. If you’re curious about debouncing AJAX calls, you can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {debounce} from 'throttle-debounce';

export default class Comp extends Component {
constructor(props) {
super(props);
this.callAPI = debounce(500, this.callAPI);
}
onChange(e) {
this.callAPI(e.target.value);
}
callAjax(value) {
console.log('value :: ', value);
// AJAX call here
}
render() {
return (
<div>
<input type="text" onKeyUp={this.onChange.bind(this)}/>
</div>
);
}
}

Optimize React for Production

A minified production build is faster and more optimized for heavy workloads, whereas a development build is meant to highlight errors and warnings in the console. Although these warnings help the developer debug errors, they can directly impact the performance of the application.

You can use the Network tab in your Developer Tools to see what is actually slowing you down. React bundle size is usually the culprit, and you need to minify it before you push it for production. If you bootstrapped the application using create-react-app, you can get a production build by running npm run build.

When you run this, create-react-app will minify the code, obfuscate/uglify it (that happens as a result of minification) and generate source maps. The end user is able to load the scripts faster than the version that was not minified. The source maps generated are particularly handy if you decide to navigate around the code through the Developer console. The source maps are loaded on demand and you can delete it before actual deployment.

If you’re using webpack, you can build a production environment by running webpack -p. React also offers production-ready single files for React and ReactDOM.

1
2
3
4
5
<script 
src="https://unpkg.com/react@16/umd/react.production.min.js">
</script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js">
</script>

There are ways to reduce the bundle size even further, like using Preact, but you need to take that path only if the initial load time is noticeably slow.

Optimizing your application’s resource is another thing that you need to consider. This has nothing to do with React, but can indeed speed up your application. Resources such as images, icons and other media files can slow down the application if they are rendered uncompressed. The Network tab in the Developer Tools should give you insights into that too. If you’re using static resources, optimize and compress them before pushing them into the server. Use a CDN if your server and the potential users are not in close proximity. Here is a nice article written by David Lazic that covers optimizing the production build further. https://medium.com/netscape/webpack-3-react-production-build-tips-d20507dba99a

Micro-optimizations To Boost React Performance

Micro-optimizations are certain details that won’t have an immediate impact on your application, but will help you in the long run. There are many-micro optimizations around that you should know about. Some of these practices also help you improve the readability and maintainability of your codebase.

Functional components vs. Class components

Functional components are easier to write and read, and they are particularly useful for building the UI or the presentational layer. Class components, on the other hand, are ideal for building containers. The containers make API calls, dispatch actions to the store, and use the state to hold data. They then pass the data as props to the presentational components.

Until React v16 was released, both the functional components and the class components had the same code path. Although the functional components appeared to have fewer lines of code, they were neither optimized nor offered speed improvements because they were converted to Class components under the hood. However, the scenario has changed with the release of V6.

Here is an excerpt from the official docs:

1
Functional components in React 16 don't go through the same code path as class components, unlike in previous versions, where they were converted to classes and would have the same code path. Class components have additional checks required and overhead in creating the instances that simple functions don't have.

Bind Function Early

1
2
3
4
5
6
7
8
9
10
11
class Button extends React.Component {
handleClick() {
console.log('Yay!');
}

render() {
return (
<button onClick={this.handleClick.bind(this)}/>
);
}
}

Arrow functions inside the render method is bad too, unfortunately.

1
2
3
4
5
6
7
8
9
10
11
class Button extends React.Component {
handleClick() {
console.log('Yay');
}

render() {
return (
<button onClick={() => this.handleClick()}/>
);
}
}

The process of creating a new function is usually fast and won’t slow you down, to be honest. But if you’ve implemented shouldComponentUpdate() or if you’re using PureComponent base class, React sees new props on each render and the child components are re-rendered unnecessarily.

To fix this issue, you can bind functions early like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Button extends React.Component {
constructor(props) {
super(props);

this.handleClick = this.handleClick.bind(this);
}

handleClick() {
console.log('Yay!');
}

render() {
return (
<button onClick={this.handleClick}/>
);
}
}

Alternatively you can use something like class instance fields. However, it’s an experimental feature that has not yet made it to the standards. But you can use it in your project with the help of Babel.

Final Words

In this post, I’ve covered some of the popular and clever ways to boost React’s performance. On a general note, you don’t have to be concerned until you start seeing actual bottlenecks. I prefer to go with the “Don’t optimize unless you need to” approach. That’s why I’d recommend benchmarking components first and measuring whether they are actually slowing you down. Your browser’s developer tools will also give insights into other non-React factors that could be affecting your app. Use that as a starting point and that should help you get started.