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.