When not to use PureComponent

When not to use PureComponent

I find React and its community to be very cool; they look out for multiple ways to provide ease of use of the framework for small and large projects. Some methods and processes that might adversely cause rerenders and lags have diverse ways to check, correct, and fit into your use case. Although, some of the methods for optimizing our react codes are not so effective.

In this article, we will learn about PureComponent, and how it optimizes our react application. We will be exploring code examples, showcasing the use of PureComponent in a class method and React.memo in a functional method. Lastly, we will understand the shortcomings of PureComponent (cases where PureComponent is not an effective solution).

Let’s get to it

What is PureComponent?

A React PureComponent renders the same output for the same state and props. Its return value is always the same for the same input values. PureComponent is one of the quickest and easiest ways to optimize React applications. It significantly improves performance by lowering the number of render operations in applications.

PureComponent and Component are two reusable bits of code that React exposes to form the foundation of a component that you want to build.

They are similar but the main difference is; Component does not implement shouldComponentUpdate() implicitly, whereas PureComponent implements it automatically but with a surface examination of properties. (which is popularly known as Shallow Comparison).

Shallow Comparison occurs when objects are compared using the strict equality ===, and it does not conduct comparisons deeper into the properties.

PureComponent optimizes your class components by returning the same result for the same props and state. It doesn’t cause a re-render until one of these changes. To achieve similar functionality in functional components, you would use React.memo method from React.

However, React.memo only returns the same result for the same props, but it doesn’t keep track of the state. We’ll learn more about this shortcoming as we proceed.

PureComponent in action (Code Samples)

Class component

A PureComponent in a class component looks like this;

// ClickCount.js
import { PureComponent } from 'react';
class ClickCount extends PureComponent {
    constructor (props) {
     super(props);
     this.state = {
        clickCount: 1,
       };
    }

    incrementClicks = () => {
        this.setState((state) => ({ clickCount: state.clickCount + 1 }));
    }

    render() {
        console.log('ClickCount component renders')
        return(
            <>
               <h1 className='CompH1'>Added Pure Component </h1>

               <div className= 'ClickCountText'> Recording {this.state.clickCount} clicks </div>
               <button className='ClickCountBtn' onClick={this.incrementClicks}>Increment</button>
            </>   
       );
    }
}

export default ClickCount;

In App.js, you use ClickCount like this:

// App.js
import { useState } from "react";
import "./App.css";
import ClickCount from "./ClickCount";

function App() {
  const [flag, setFlag] = useState(true);
  //const [users, setUsers] = useState([]);
  console.log("App components rendered");

  return (
    <div className="MainClassPure">
      <div className="ToggledText">
        Click to increase {flag ? "count!" : "more count!"}
      </div>
      <button className="TogglingBtn" onClick={() => setFlag(!flag)}>
        Toggle Text!
      </button>
      <ClickCount />
    </div>
  );
}

export default App;

Play with sandbox:

codesandbox.io/s/purecomponent-mizkwg

In this code, we’ve used PureComponent for the ClickCount component. This means that the ClickCount component will only rerender if the props changes (which in our example is the text prop) or if the state changes (which in our example is the ClickCount) state.

Since ClickCount is a child of the App component, when the flag state in the App component changes, everything in the App component–its children, including ClickCount–ideally is supposed to rerender. But because we have used PureComponent (instead of Component) for ClickCount, ClickCount will not rerender if the flag changes. It will only rerender if the text prop or the ClickCount changes.

You can test this by clicking “Toggle Flag”. You will notice that console.log(“App components rendered”) in App.js runs again (after it was run the first time) because the state changes. But, console.log("ClickCount component renders") in ClickCount.js does not run. Regardless of how many times “Toggle Flag” is clicked, it does not affect ClickCount.js.

But if you click “Increment” in ClickCount.js, the ClickCount state is updated, and then, ClickCount rerenders. Therefore, console.log("ClickCount component renders") is run again.

Let’s replicate this in functional component

Functional Component

//Memo.js 
import React from 'react'

function Memo ( { name }) {
    console.log ('Rendering the Memo Component')

    return (
        <div>
            {name}
        </div>
    )
}

export default React.memo(Memo);
// MemoParent
import React, {Component} from 'react'
import Memo from './Memo'

class MemoParent extends Component {
    constructor(props){
        super(props)

        this.state = {
            name: 'billy'
        }
    }

    componentDidMount() {
        setInterval(() => {
            this.setState({
                name: 'billy'
            }) 
        }, 2000)
    }

    render(){
        console.log ('Memo Parent component renders')
        return (
            <div className='nameValue'>
                {
                   <Memo name={this.state.name} />
                }
            </div>
        )
    }
}

export default MemoParent

codesandbox.io/s/react-memo-function-method..

The one below has the React.memo in place, and we still get a continuous interval update because the values are changing. Which means PureComponent(React.memo) is not valid.

import React, {Component} from 'react'
import Memo from './Memo'


class MemoParent extends Component {
    constructor(props){
        super(props)

        this.state = {
            name: 'billy'
        }
    }

    componentDidMount() {
        setInterval(() => {
            this.setState({
                name: this.state.name + '!'   
            }) 
        }, 2000)
    }

    render(){
        console.log ('Memo Parent component renders')
        return (
            <div className='nameValue'>
                {
                   <Memo name={this.state.name} />
                }
            </div>
        )
    }
}

export default MemoParent

codesandbox.io/s/function-react-memo-slumq0..

Shortcomings of PureComponent (When you should not use Pure Component)

In cases where objects are used for props or state (The problem of Shallow Comparison)

The shallow comparison works by checking if two values are equal. For primitive types like strings and numbers, it checks equality, while it checks reference for objects and arrays. In practice, shallow comparison checks for superficial equality (===) between current and next state objects and between current and next props objects. You can get a more reliable output with the primitive than non-primitive data type as objects with the same properties compared together would be different because their references are different. Consider this popular reference example

let a = { location: {city: ‘Ohio’}};
let b = { location: {city: ‘Ohio’}};
a === b; false (This is because the references are different)

But if you say

let c = a
a === c; // true (because the references are the same)

So in a case where a prop or a state property is a primitive value, if the values remain the same, PureComponent would ensure that you do not get a rerender; But if such value is an object, you will get rerenders. For example, consider this code:

<ClickCount text={{full: “JavaScript”, short: “js”}} />

class ClickCount extends PureComponent {
   constructor(props){
        super(props)

        this.state = {
            text: {
              full: “JavaScript”,
              short: “js”
        }
    }

}

In these examples, you can see that a text prop is an object, as well as the text state in the component. Here, shallow comparison would occur, and even if the same properties exist in the objects being compared, PureComponent would believe that they are not equal, and will cause a rerender.

The process or scheme of work of shallow comparison is not reliable so don’t go for PureComponent if you would be using objects for prop values or state values.

The type of data you use for state and props can cause inconsistencies with PureComponent, as you lose the optimization you are hoping for; so if you are not sure of the data you are expecting, then most likely PureComponent is not a good solution to your use case.

To optimize State in Functional Component

As we have seen earlier, React.memo for functional components allows you to replicate the optimization that PureComponent provides in class components. But, with a drawback React.Memo does optimization for only props–not state.

React.memo is a Higher Order Component (HOC), takes another component and returns an optimized version of the component.

When you are passing an empty array as props

When you pass an empty array to props data in your code it will return false and lead to unnecessary re-renders. Consider the following examples

render(){
        return (

<Memo name={this.state.name || [] } />

        )
    }

An empty array will always create a new reference every time the parent re-renders.

A workaround would be to create a reference for passing the props (Create a static reference, pass it the props).

This return value will be ‘true‘ because the static reference will make the swallow comparison return true. Like this….

const ArrayIsEmpty = [];
 render(){
        return (

<Memo name={this.state.name || ArrayIsEmpty } />

        )
    }

Final Thoughts

Overall, using only PureComponent for the optimization of your React application is not very effective as this can result in inconsistencies and bugs due to shallow comparison. This article doesn’t aim to stop you from using PureComponent but addresses the point that PureComponent is not reliable enough for optimization in React. You could use React hooks, memoize your function, use immutable data structures, and so on for better optimization.

Further Reading

React Hooks: The Deep Cuts - CSS-Tricks