Optimize performance with this lesser-known component class
Photo by Brett Jordan on Unsplash
If you?ve used React, then you?re familiar with React.Component. It’s probably what you’re extending every time you create a new stateful component. App.js is a class component that does just that:
import React from ‘react’;export default class Parent extends React.Component { constructor(props) { super(props); this.state = { message: ‘hello’, }; } render() { return ( <main> {this.state.message} </main> ); }}
But there is another lesser-known component class that you can extend called React.PureComponent. What are the differences between these two classes, and why would you want to use PureComponent?
React.PureComponent Is Primarily Used for Performance Optimization
As outlined in the React docs:
If your React component?s render() function renders the same result given the same props and state, you can use React.PureComponent for a performance boost in some cases.
What exactly does that mean? Let?s say that we have a child component, child.js:
import React from ‘react’;const Child = props => <div>{ props.message }</div>;export default Child;
This component receives props from its parent. We want this component to re-render whenever it receives new props. But what if its parent is tracking different kinds of states? Will the parent component trigger a re-render of the child component even if the child?s received props did not change?
Let?s say the parent component looks like this:
import React from ‘react’;import Child from ‘./Child’;export default class Parent extends React.Component { constructor(props) { super(props); this.state = { counter: 0, message: ‘hello’, }; } render() { return ( <main> {this.state.counter} <br /> <Child message={this.state.message} /> </main> ); }}
Parent.js renders a <main> element that displays two sets of state, counter and message. The message state is passed to the <Child /> component and is rendered there.
Taking a closer look at the state in the constructor method, the parent is keeping track of our state here:
this.state = { counter: 0, message: ‘hello’};
Given these components, if we run yarn start, we get this result in the viewport:
0hello
0 is rendered from the parent component and hello is rendered from the child component.
We have both counter and message props that are set in Parent.js. Looking at Child.js again, you can see that it’s only receiving the message prop. If the counter prop changes, we don’t want Child.js re-rendering, since counter has nothing to do with Child.js. For a tiny project like this, it may not make much of a difference worrying about re-renders on this level, but unnecessarily re-rendering bunches and bunches of child components could handicap the performance of a larger project.
There are some things we can do to test when our child component re-renders. This YouTube video shows that by putting console.log() statements in each of our components, we can check to see which ones re-render after certain events.
Let?s test this out with our parent and child components. First, let?s add a console.log() to our parent component. Let’s also provide a way to alter our counter state:
import React from ‘react’;import Child from ‘./Child’;export default class Parent extends React.Component { constructor(props) { super(props); this.state = { counter: 0, message: ‘hello’, }; } handleClick = () => { this.setState(prevState => ({ counter: prevState.counter + 1 })); }; render() { console.log(‘parent rendered’); return ( <main> {this.state.counter} <br /> <Child message={this.state.message} /> <button type=’button’ onClick={this.handleClick}>Increment</button> </main> ); }}
We?ve added a handleClick() method that’s attached to a <button>. This way, we can increment the counter state, which will trigger a re-render of the parent component.
If we click the button a few times and check the console, we?ll see that the parent component is re-rendering every time the counter state is incremented:
parent renderedparent renderedparent rendered…
So far, so good. Let?s also add a console.log() statement to the child component and check to see if it’s also re-rendering every time counter is incremented:
import React from ‘react’;export default class Child extends React.Component { render() { console.log(‘child rendered’); return ( <div>{ this.props.message }</div> ); }}
I turned the child component into a class component simply to add a console.log() statement inside of it. In the real world, this isn’t a good enough reason to make this component a class. Nonetheless, despite the console.log() statement, this version of the child component is basically the same as the stateless component.
Now when we increment in the browser, we get this in the console:
Parent renderedChild renderedParent renderedChild rendered…
Given that these console.log() statements only fire when the render() methods are called, we can definitively say that changing the counter state is unnecessarily re-rendering the child component. This is where React.PureComponent comes in. If we instead extend the child component from React.PureComponent, then we’ll stop it from unnecessarily re-rendering:
import React from ‘react’;export default class Child extends React.PureComponent { render() { console.log(‘child rendered’); return ( <div>{ this.props.message }</div> ); }}
Now when we increment the counter state, we get this in the console:
parent renderedchild renderedparent renderedparent renderedparent rendered…
We?ve successfully prevented unnecessary re-render in the child component.
What?s Under the Hood?
The big difference between Component and PureComponent is that PureComponent automatically implements the shouldComponentUpdate() lifecycle method.
Right in the docs for shouldComponentUpdate(), it states:
Use shouldComponentUpdate() to let React know if a component?s output is not affected by the current change in state or props. The default behavior is to re-render on every state change, and in the vast majority of cases you should rely on the default behavior.
So, there you have it ? as our console.log() experiment shows, the default behavior of our components is to re-render on every state change. shouldComponentUpdate() is a checkpoint before this automatic re-rendering, evaluating whether the change in state has anything to do with the component at hand.
Yes, you can implement shouldComponentUpdate() manually, but PureComponent does this implicitly, and sometimes it?s exactly what you’re looking for. You could see how switching dozens of stateful children to extending PureComponent could prevent lots of unnecessary re-renders.
Should You Use PureComponent As Much As Possible?
Sometimes, it won?t make much sense to use PureComponent, even if it does help optimize performance. Given our example above, there wasn’t really much of a need to make Child a stateful class component other than to use a console.log(). If there’s no reason for it being a class, then it won’t be extending either Component or PureComponent, and turning it into a class just to use PureComponent is unnecessary.
However, if children components are classes and are being passed only some of the props of their parents, then PureComponent might be worth looking into.