React uses virtual DOM to re-render a component when its state
or props
updates. This is how a single-page React app brings you dynamic content based on user interaction without refreshing the page. However, every time you re-render a component, you lose some performance benefits in an app. Hence, there's a need to optimize the mechanism of reconciliation or updating the DOM by tracking your updated state
and props
.
This guide focuses on how to optimize the performance of a React app using pure components and eventually control the re-rendering of a component to leave the contents of the component alone.
Pure components are special components in React that render only when the state
or props
changes using the lifecycle methods shouldComponentUpdate()
and componentDidUpdate()
implicitly. Have a look at the following code in React, which updates the state every second:
1import React from 'react';
2
3export default class Counter extends React.Component{
4 constructor(){
5 super();
6 this.state={
7 count: 0
8 }
9
10 this.componentDidUpdate=()=>{
11 console.log('rendered')
12 }
13 setInterval(()=>{
14 this.setState({
15 count: 0
16 })
17 },1000)
18}
19
20 render(){
21 return(
22 <div>
23 <h2>Counter value: {this.state.count}</h2>
24 </div>
25 )
26 }
27}
The above class component has count
inside its state
initialized to 0
and updates the count
to the same value every one second. The state is updated, but its value doesn't change. However, the app re-renders every time setState
is called, thereby recursively running into an infinite loop of re-renders.
In order to combat this, you can use pure components, which compare the previous state value with the updated state value and decide if the component should re-render or not. To break the infinite re-rendering loop in the above code, simply add the keyword Pure
when creating the component
.
1export default class Counter extends React.PureComponent{
2 ...
3}
Now the component only mounts but never re-renders. Pure components do solve the problem of unnecessary re-renders, but not entirely. They do a shallow comparison between the previous state
or props
and the next state
or props
. This means that React cannot fully understand whether state
or props
have actually changed and could still give some unnecessary re-renders.
You can gain full control over your component's re-rendering condition using a lifecycle method called shouldComponentUpdate()
. It has access to nextProps
andnextState
as first and second parameters, respectively, and returns a Boolean value based on whether it decides to re-render the component or not. You can write your own function that does a deep comparison between two objects and call it inside this method, or you can directly perform the deep comparison inside it. Extending the above example using shouldComponentUpdate()
:
1import React from 'react';
2
3export default class Counter extends React.Component{
4 constructor(){
5 super();
6 this.state={
7 count: 0
8 }
9
10 this.componentDidUpdate=()=>{
11 console.log('rendered')
12 }
13
14 this.shouldComponentUpdate=(nextProps,nextState)=>{
15 return nextState.count!=this.state.count
16 }
17 setInterval(()=>{
18 this.setState({
19 count: 0
20 })
21 },1000)
22}
23
24 render(){
25 return(
26 <div>
27 <h2>Counter value: {this.state.count}</h2>
28 </div>
29 )
30 }
31}
The component doesn't render now since a deep comparison is performed between the nextState
and the current state
. React doesn't recommend using this method unless you are sure that this is the only way to go.
Earlier, you couldn't do a performance optimization in a stateless functional component as pure components must always be class components. You can now use a higher-order component called memo
to implement a pure component in a function component. The term memo
is a derivative of the term memoization, which is a technique for storing computed results somewhere and returning them if the same computations are repeated. Under the hood, your browser's cache implements memoization when it loads assets of a particular website faster the second time you visit it. React.memo
is a higher-order component that tells a function component to only re-render when the props
change, essentially making it a pure component.
Consider the following higher-order component that returns a random color to a child component each second. The child component utilizes this random color to style the h1
tag. Since you are generating a random value from a fixed set of values quite often, there are chances you might run into a previously computed value. In other words, your random colors can be repeated. This is a classic example of memoization. To use memo
, wrap your function component in a call to React.memo
, and React will skip rendering the component using the last rendered result. Consider the following code:
1import React,{memo} from 'react';
2
3const Name = memo(({ color}) => <>
4 <h1 style={{color:`${color}`}}>Hello Campers! How you doing?</h1>
5</>);
6
7export default class ColorsHOC extends React.Component {
8 colors = ["Red", "Green", "Blue"];
9 state = { color: '' };
10
11 componentDidMount() {
12 setInterval(() => {
13 const color = this.getRandomColor();
14 this.setState({ color});
15 }, 1000);
16 }
17
18 getRandomColor = () =>
19 this.colors[Math.floor(Math.random() * this.colors.length)];
20
21 render() {
22 return <Name color={this.state.color} />;
23 }
24}
Remember that React.memo
only checks for prop
changes and not state
changes. Using useContext
or useState
inside React.memo
will still cause your function component to render when the state changes.
By default, React.memo
does only a shallow comparison of props
object. You can pass a custom comparison function as the second argument, as shown below:
1function MyComponent(props) {
2
3}
4function areEqual(prevProps, nextProps) {
5 //compare here
6}
7export default React.memo(MyComponent, areEqual);
Components that are not supposed to re-render as often should be made into pure components to save unnecessary renders. Through the methods discussed in this guide, you can ignore a subtree of your component hierarchy and gain considerable performance benefits for your app. However, be careful while using them as their improper use can lead to bugs in your app.