DEV Community

Cover image for React Optimised  Components
AnkitPat
AnkitPat

Posted on

React Optimised Components

Everyone wonders why react web app runs slow? Answer to these often lies within component only, when and how much its re-rendering. Are those re-render are even necessary? React doesn't provide you magical performance upgrade, it just gives you tools and way so by which you can optimise it. Now it's on us when and how to use it. Let's get going...

So since react introduction of virtual DOM, it changed the way of thinking in web developers. With Virtual DOM, react makes UI update as efficient as it should be expected.

Now, to make React app acts how it should be then you got to understand. How React components renders? How it went through all lifecycle methods in different phases? How and when to use which lifecycle method?

With react, you can gain alot of performance improvement that it has to offer by measuring and computing performance. And, React provides just the tools and functionalities necessary to make this easy.

So, lets start with How React works?

How does React works?

Before we check on optimisation techniques, lets check how react actually works. At the start of React development, you have the simple and obvious JSX syntax, and React’s ability to build and compare virtual DOMs. Since its release, React has influenced many other front-end libraries. Libraries such as Vue.js also rely on the idea of virtual DOMs.

Here is how React works:

Each React application begins with a root component, and is composed of many components in a tree formation. Components in React are “functions” that render the UI based on the data (props and state) it receives.

We can symbolize this as F.

UIView = F(data)

Users interact with the UI and cause the data to change. Interaction can involves clicking a button, tapping on an image, dragging list items around, AJAX requests invoking APIs, etc., all these only changes the data. They never cause the UI to change directly.

Image description

Here, data defines state of web application, not just what you have stored in your database. Even bits of frontend states like checkbox state or tab selection are all part of data.

Whenever there is a change in this data, React uses the component functions to re-render the UI, but only virtually:

UI1 = F(data1)
UI2 = F(data2)

Now React compares the difference between new UI with old UI like these:

Changes = Diff(UI1, UI2)

Now after the difference found, react will only apply those difference to the Real UI Browser. This process is called as Reconciliation .

Image description

This repeated process of diffing and applying changes to browser is going on for every data or state changes in application. These continuous changes and rendering may be one of primary source of performance issue in any React app. Building a React app where the diffing algorithm fails to reconcile effectively, causing the entire app to be rendered repeatedly can result in a frustratingly slow experience.

How to work on Optimising?

First question is, where exactly we can optimize?

As you know, during the initial render process, React builds a DOM tree like this:

Image description

Given a part of the data changes, what we want React to do is re-render only the components that are directly affected by the change (and possibly skip even the diff process for the rest of the components):

Image description

However, what React ends up doing is:

Image description

Here, all of the green nodes are rendered and diffed, resulting in wasted time/computation resources. This is where we will primarily put our optimization efforts in. Configuring each component to only render-diff when it is necessary will allow us to reclaim these wasted CPU cycles.

Things we can do for optimisation:

1. ShouldComponentUpdate() ?

As your app grows, attempting to re-render and compare the entire virtual DOM at every action will eventually slow down.

React provides a lifecycle method which can help you in stopping rendering of component which is not actually required to re-render if certain data/state changes.

function shouldComponentUpdate(nextProps, nextState) {
return true;
}

By default nature of these method is to return true always. Which means giving permission to re-render of component on every change.

We can modify these method to return false so it will re-render component. But this is not the perfect way to stop re-render. Because it will stop re-rendering of every data change.

So let's do it more perfect way. You can compare nextState with current state and nextProps to current props. Like these:

function shouldComponentUpdate(nextProps, nextState) {
return nextProps.Id !== this.props.Id;
}

2. Using a React.PureComponent

To ease and automate a bit this optimization technique, React provides what is known as “pure” component. A React.PureComponent is exactly like a React.Component that implements a shouldComponentUpdate() function with a shallow prop and state comparison.

A React.PureComponent is more or less equivalent to this:

class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return shallowCompare(this.props, nextProps) &&
shallowCompare(this.state, nextState);
}

}

As you see it only do shallow comparison, so it will be only effective if your props and state contains primitive data.

3. Making data immutable

Assuming you have used React.PureComponent but you still have ways where we have complex data set and cannot be detected by shallow comparison. Other workaround is create Immutable objects.

The idea behind using immutable data structures is simple. Whenever an object containing complex data changes, instead of making the changes in that object, create a copy of that object with the changes. This makes detecting changes in data as simple as comparing the reference of the two objects.

You can use Object.assign or _.extend (from Underscore.js or Lodash):

const newValue2 = Object.assign({}, oldValue);
const newValue2 = _.extend({}, oldValue);

Even better, you can use a library that provides immutable data structures:

var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1.equals(map2) === true);
var map3 = map1.set('b', 50);
assert(map1.equals(map3) === false);

Here, Immutable.Map is provided by the library Immutable.js.

Every time a map is updated with its method set, a new map is returned only if the set operation changed the underlying value. Otherwise, the same map is returned.

4. Using the production build checks

When developing a React app, you are presented with really useful warnings and error messages. These make identifying bugs and issues during development a bliss. But they come at a cost of performance.

If you look into React’s source code, you will see a lot of if (process.env.NODE_ENV != 'production') checks. These chunks of code that React is running in your development environment isn’t something needed by the end user. For production environments, all of this unnecessary code can be discarded.

If you bootstrapped your project using create-react-app, then you can simply run npm run build to produce the production build without this extra code. If you are using Webpack directly, you can run webpack -p (which is equivalent of
webpack --optimize-minimize --define process.env.NODE_ENV="'production'".

5. Binding function with Context

It is very common to see functions bound to the context of the component inside the render function. This is often the case when we use these functions to handle events of child components.

// Creates a newhandleUploadfunction during each render()
<Header onLogoClick={this.handleClick.bind(this)} />
// ...as do inlined arrow functions
<Header onLogoClick={event => this.handleClick(event)} />

This will cause the render() function to create a new function on every render. A much better way of doing the same is:

class App extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
render() {
...

...
}
}

Top comments (0)