React: Improve the performance by using PureComponents

In this post we're going to look at PureComponents in React.

So what is a PureComponent?

A PureComponent is just the same as a normal react component except for one thing — the implementation of shouldComponentUpdate. A PureComponent only does a shallow comparison of the props and state to decide if it should re-render.

So what does this mean? Well it's quite simple — primitives like numbers, booleans and strings are checked as normal. Objects however is a bit different — they are checked by reference. This means that every time the object reference change in either the props or state, the component will re-render.

This can boost the performance, because now we don't have to check all the properties in an object to figure out if we want to re-render a given component.

Creating a PureComponent

Now that we got an idea of what PureComponents can do — let's make one!

Say we want to make a counter component that displays the value of a counter object provided as a prop.

class Counter extends React.PureComponent {

  render() {
    return <p>Count: {this.props.counter.count}</p>
  }
}

As you can see, the only way this component differs from a normal react component is that it extends React.PureComponent. That's all we need to do to make it use the shallow comparison that PureComponent provides.

One thing we need to make sure of however is that we embrace immutability.

Why mutable objects and PureComponents won't fly

If we do changes to objects in a mutable way, the object reference won't change. This means that a PureComponent wouldn't recognize changes since it's only checking if the object reference is the same.

Let's prove this by creating a parent component for our counter component that provides the counter object. The parent will then update the counter object every second in a mutable way.

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      counter: { count: 1 }
    };
  }

  mutableIncrement() {
    this.setState((prev) => {
      prev.counter.count = prev.counter.count + 1;
      return prev;
    });
  }

  render() {
    setTimeout(() => this.mutableIncrement(), 1000);
    return <Counter counter={this.state.counter} />
  }
}

If we now execute the code, we'll see that the counter doesn't increment — this because the object reference stays the same.

See the Pen Why PureComponent and mutable objects won't fly by Marius Herring (@mariusherring) on CodePen.

Embracing immutability

Now that we see why mutable objects and PureComponents is a bad match, let's rewrite the parent component to embrace immutability when incrementing the counter.

class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      counter: { count: 1 }
    };
  }

  increment() {
    this.setState((prev) => ({
      counter: {
        count: prev.counter.count + 1
      }
    }))
  }

  render() {
    setTimeout(() => this.increment(), 1000);
    return <Counter counter={this.state.counter} />
  }
}

If we now try to run the immutable version, our PureComponent will pick up the changes — this because a change to the counter object means a totally new object and object reference.

See the Pen Combining PureComponents and immutable objects by Marius Herring (@mariusherring) on CodePen.

Enjoyed the post?

If you don't want to miss future posts, make sure to subscribe