There are many performance optimization concepts in react which helps us to build faster applications.
Today I am going to discuss one of most discussed concept in performance optimization: how to avoid Reconciliation
DOM object
in memory which is called Virtual DOM:Basically it is just a javascript object which keeps information of every DOM object.
[Imagine virtual DOM as a javascript object which keeps information about every element]
{
node: 'html',
properties: {
attributes: [],
classes: [],
events: []
},
children: {
node: 'body'
properties: {
attributes: [],
classes: [],
events: []
},
children: [
{
node: 'div',
properties: {
attributes: [],
classes: [],
events: []
},
},
{
node: 'button',
properties: {
attributes: [],
classes: [],
events: []
}
}
]
}
}
virtual DOM
snapshot of new object with last updated object. This way React has to update only changed nodes in React DOM.This whole process is how React updates DOM which is called
Reconciliation
.
This process is way faster than Real DOM manipulation.
Even though React is clever enough to update only changed nodes. But when props and state changes, re-rendering takes place which takes some time.
So we need to avoid unnecessary re-rendering for such cases.
When parent component renders, all its child components are re-rendered even if their props or states didn’t change.
And if child component contains a very slow computation then, it will be computed in every re-render.
[Our Case Example-1 ]
color
which is changed by selecting a color from select element.Even if you change the counter value using button element in Parent, the child component re-renders hence the computation along with it takes place which is quite noticable in example below.
Parent Component:
import React from 'react';
import Child from './Child';
class Parent extends React.Component {
constructor() {
super();
this.state = {
counter: 0,
color: 'red'
}
}
render() {
console.log('[Parent] rendered');
return (
<div className="container">
<div>Counter: {this.state.counter}</div>
<button onClick={() => this.setState({counter: this.state.counter + 1})}>Click me to change counter</button>
<select defaultValue="red" onChange={(e) => this.setState({color: e.target.value})}>
<option value="red">red</option>
<option value="blue">blue</option>
<option value="grey">grey</option>
</select>
<Child color={this.state.color} />
</div>
)
}
}
export default Parent;
import React from 'react';
class Child extends React.Component {
delay() {
console.log('[Delay] function called');
for(let i = 0; i < 5000000000; i++) {
i++;
}
return 'delayed text';
}
render() {
console.log('[Child] rendered');
return (
<div className="child">
<hr />
<div>{this.delay()}</div>
Selected Color: {this.props.color}
</div>
)
}
}
export default Child;
That’s where shouldComponentUpdate()
comes in. It is the lifecycle method which is called before re-rendering starts in class based components.
shouldComponentUpdate(nextProps, nextState)
true
by default which means component will re-render by default.[Example:]
/* Assume props = {color: 'red'} state={counter: 0} */
class Parent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
if(this.nextProps.color === this.props.color) {
return false;
}
if(this.nextState.counter === this.state.counter) {
return false;
}
return true;
}
so if the color prop and counter variable in state doesn’t change then this component won’t re-render.
Now we use shouldComponentUpdate() to compare the props and return false if props doesn’t changes.
So, now Child Component with shouldComponentUpdate:
import React from "react";
class Child extends React.Component {
delay() {
console.log("[Delay] function called");
for (let i = 0; i < 800000000; i++) {
i++;
}
return "delayed text";
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color === nextProps.color) {
return false;
} else {
return true;
}
}
render() {
console.log("[Child] rendered");
return (
<div className="child">
<hr />
<div>{this.delay()}</div>
Selected Color: {this.props.color}
</div>
);
}
}
export default Child;
I have added the codesandbox urls below for both state of application before optimization and after optimization with shouldComponentUpdate().
In the example I have also added consoles so that we can observe which component are rendered and re-rendered.
2. With shouldComponentUpdate():
React also provides React.PureComponent
which does shallow comparision of props and state to skip the re-rendering.
Using this we don’t have to write shouldComponentUpdate() manually, PureComponent internally manages it.
Example-1: our case when delay() function should be skipped if prop or state does not changes. We can achieve it using PureComponent.
import React from "react";
class Child extends React.PureComponent {
delay() {
console.log("[Delay] function called");
for (let i = 0; i < 800000000; i++) {
i++;
}
return "delayed text";
}
render() {
console.log("[Child] rendered");
return (
<div className="child">
<hr />
<div>{this.delay()}</div>
Selected Color: {this.props.color}
</div>
);
}
}
export default Child;
It is much clearer code and does the work if shallow comparision works fine.
But it does not work if there is complex data structure or state is mutated in any way [Like exmaple below]:
Parent Component:
import React from "react";
import Child from "./Child";
class Parent extends React.Component {
constructor() {
super();
this.state = {
color: ["red"]
};
}
render() {
return (
<div className="container">
<select
onChange={(e) => {
console.log(this.state.color);
let updatedColor = this.state.color;
if (Array.isArray(this.state.color)) {
updatedColor.push(e.target.value);
}
return this.setState({ color: updatedColor });
}}
>
<option value="red">red</option>
<option value="blue">blue</option>
<option value="grey">grey</option>
</select>
<Child color={this.state.color} />
</div>
);
}
}
export default Parent;
import React from "react";
class Child extends React.PureComponent {
render() {
return (
<div className="child">
<hr />
Selected Color:
<ul>
{this.props.color.map((el, index) => {
return <li key={index}>{el}</li>;
})}
</ul>
</div>
);
}
}
export default Child;
Here child component doesn’t re-render even if you change the values in colors array.
Array is a reference type data struture in javascript and because PureComponent only does shallow comparision and both references are same each time.
To avoid this we can avoid mutation and send new array or objects each time.
Example-1: our case when delay() function should be skipped if prop or state does not changes. We can achieve it using memo().
import React from "react";
function Child (props) {
function delay() {
for (let i = 0; i < 800000000; i++) {
i++;
}
return "delayed text";
}
return (
<div className="child">
<hr />
<div>{delay()}</div>
Selected Color: {props.color}
</div>
);
}
export default React.memo(Child);
just wrap your component in React.memo() and it does shallow comparision on props.
Note by React docs: All the above methods only exists as a performance optimization. Do not rely on it to prevent a render, as this can lead to bugs
References:
React docs
Thanks for reading the article!
If you any questions, please comment here.
For more tech blogs be sure to follow us on twitter & linkedin.
India
86P, 4th Floor, Sector 44, Gurugram, Haryana 122003Singapore
#21-02, Tower 2A, The Bayshore condo, Singapore 469974Canada
8 Hillcrest Avenue Toronto ON M2N 6Y6, CanadaUS
31 River CT, Jersey City, New JerseySubscribe to our newsletter
Our Services
Top Reads
India
86P, 4th Floor, Sector 44, Gurugram, Haryana 122003
Singapore
#21-02, Tower 2A, The Bayshore condo, Singapore 469974
Canada
8 Hillcrest Avenue Toronto ON M2N 6Y6, Canada
US
31 River CT, Jersey City, New Jersey
Contact us
info@primathon.in
+91-9205966678
Reviews