Author avatar

Gaurav Singhal

How to Leave the Contents of a React Component Alone

Gaurav Singhal

  • Aug 19, 2020
  • 7 Min read
  • 215 Views
  • Aug 19, 2020
  • 7 Min read
  • 215 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

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.

Using Pure Componentst

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';

export default class Counter extends React.Component{
  constructor(){
    super();
    this.state={
      count: 0
    }
  
    this.componentDidUpdate=()=>{
      console.log('rendered')
    }
  setInterval(()=>{
    this.setState({
      count: 0
    })
  },1000)
}

  render(){
    return(
      <div>
        <h2>Counter value: {this.state.count}</h2>
      </div>
    )
  }
}
jsx

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 .

1
2
3
export default class Counter extends React.PureComponent{
	...
}
jsx

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.

Using shouldComponentUpdate() Explicitly

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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from 'react';

export default class Counter extends React.Component{
  constructor(){
    super();
    this.state={
      count: 0
    }

    this.componentDidUpdate=()=>{
      console.log('rendered')
    }
  
    this.shouldComponentUpdate=(nextProps,nextState)=>{
      return nextState.count!=this.state.count
    }
  setInterval(()=>{
    this.setState({
      count: 0
    })
  },1000)
}

  render(){
    return(
      <div>
        <h2>Counter value: {this.state.count}</h2>
      </div>
    )
  }
} 
jsx

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.

Hooks Implementation of Pure Components

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React,{memo} from 'react';

const Name = memo(({ color}) => <>
 <h1 style={{color:`${color}`}}>Hello Campers! How you doing?</h1>
</>);

export default class ColorsHOC extends React.Component {
  colors = ["Red", "Green", "Blue"];
  state = { color: '' };

  componentDidMount() {
    setInterval(() => {
      const color = this.getRandomColor();
      this.setState({ color});
    }, 1000);
  }

  getRandomColor = () =>
    this.colors[Math.floor(Math.random() * this.colors.length)];

  render() {
    return <Name color={this.state.color} />;
  }
}
jsx

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:

1
2
3
4
5
6
7
function MyComponent(props) {

}
function areEqual(prevProps, nextProps) {
 //compare here
}
export default React.memo(MyComponent, areEqual);
jsx

Conclusion

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.

3