Glad Chinda
Follow
Full-stack web developer learning new hacks one day at a time. Web technology enthusiast. Hacking stuffs @theflutterwave.
· 6 min read

Pure Functional Components in React 16.6

Memoizing functional components with the React.memo() API

Pure Functional Components in React 16.6

Speaking about JavaScript frameworks at such a time as this can never be complete if nothing is said about the React framework created and maintained by the team at Facebook.

React is used massively in tons of production apps out there, running on varying platforms ranging from web to mobile devices.

This article introduces the new React.memo() API which was added in React 16.6. You can keep up with the changes and suggestions for the React framework on the React RFCs repository.


React components

Like most modern JavaScript frameworks, React is a component-based framework. In React terms, a component is usually defined as a function of its state and props.

If you have used React for some time, you should already know that it supports two flavors of components, namely: class components and functional components.

The following code snippet shows a simple ReactHeader component defined as both a class component and a functional component:

// CLASS COMPONENT
class ReactHeader extends React.Component {
  render() {
    return (
      <h1>
        React {this.props.version || 16} Documentation
      </h1>
    )
  }
}


// FUNCTIONAL COMPONENT
function ReactHeader(props) {
  return (
    <h1>
      React {props.version || 16} Documentation
    </h1>
  )
}

Pure components

Based on the concept of purity in functional programming paradigms, a function is said to be pure if:

  • its return value is only determined by its input values.
  • its return value is always the same for the same input values.

A React component can be considered pure if it renders the same output for the same state and props. For class components like this, React provides the PureComponent base class. Class components that extend the React.PureComponent class are treated as pure components.

Pure components have some performance improvements and render optimizations since React implements the shouldComponentUpdate() method for them with a shallow comparison for props and state.

The following code snippet shows a very simple, pure React component:

import React from 'react';

class PercentageStat extends React.PureComponent {

  render() {
    const { label, score = 0, total = Math.max(1, score) } = this.props;

    return (
      <div>
        <h6>{ label }</h6>
        <span>{ Math.round(score / total * 100) }%</span>
      </div>
    )
  }

}

export default PercentageStat;

Pure functional components

Functional components are very useful in React, especially when you want to isolate state management from the component. Which is why they are often called stateless components.

However, functional components cannot leverage on the performance improvements and render optimizations that come with React.PureComponent since they are not classes by definition.

In fact, if you have a functional component, and you want React to treat it as a pure component, you will have to convert the functional component to a class component that extends React.PureComponent.

Here is a simple example (still using the PercentageStat component from before):

// FUNCTIONAL COMPONENT
function PercentageStat({ label, score = 0, total = Math.max(1, score) }) {
  return (
    <div>
      <h6>{ label }</h6>
      <span>{ Math.round(score / total * 100) }%</span>
    </div>
  )
}


// CONVERTED TO PURE COMPONENT
class PercentageStat extends React.PureComponent {

  render() {
    const { label, score = 0, total = Math.max(1, score) } = this.props;

    return (
      <div>
        <h6>{ label }</h6>
        <span>{ Math.round(score / total * 100) }%</span>
      </div>
    )
  }

}

Using { pure } HOC from Recompose

Optimizing a functional component so that React can treat it as a pure component shouldn’t necessarily require that the component be converted to a class component.

If you are already familiar with the recompose package then you know that it provides a wide collection of higher-order components that makes it very useful when dealing with functional components.

The recompose package exports a {pure} higher-order component that tries to optimize a React component by preventing updates on the component unless a prop has changed, using shallowEqual() to test for changes.

Using the pure higher-order component, our functional component can be wrapped as follows:

import React from 'react';
import { pure } from 'recompose';

function PercentageStat({ label, score = 0, total = Math.max(1, score) }) {
  return (
    <div>
      <h6>{ label }</h6>
      <span>{ Math.round(score / total * 100) }%</span>
    </div>
  )
}

// Wrap component using the `pure` HOC from recompose
export default pure(PercentageStat);

Introducing React.memo() in React 16.6

Although the React framework has gone through several iterations of changes over the years, a couple more improvements and additions are still being made to the framework on a regular basis. You can see the change logs for the framework in the official React repository.


Formerly React.pure()

A few days ago, Dan Abramov from the core React team tweeted about some additional features that were undergoing review for the next minor release of React (v16.6.0).

Here is the tweet:

Just published an RFC for React.pure() which lets you optimize function components - similar to PureComponent in classes. Feedback welcome! https://github.com/reactjs/rfcs/pull/63

 — @dan_abramov

One of the new features was the React.pure() API, which provides a means of optimizing functional components in a much similar fashion as how class components can be optimized using React.PureComponent.

The React.pure() API is available from React 16.6.0-alpha.400d197. However, it has been changed to React.memo() in React 16.6.


Now React.memo() in React 16.6

Several thoughts went into giving an appropriate name to this new React optimization feature. Naming it pure() will make it to be confused with the concept of pure functions in functional programming, whereas it basically memoizes functional components.

Hence in the final release of React 16.6, the React.pure() API has been renamed as React.memo().

You can checkout the changes and comments about the React.pure() API on the React RFCs repository.


Implementation Details

There are a few things worth knowing about the implementation of the React.memo() API:

  1. React.memo() is a higher-order component. It takes a React component as its first argument and returns a special kind of React component.
  2. React.memo() returns a special React component type — that allows the renderer to render the component while memoizing the output. Hence, bailing out of updates if the component’s props are shallowly equal.
  3. React.memo() works with all React components. The first argument passed to React.memo() can be any type of React component. However, for class components, you should use React.PureComponent instead of using React.memo().
  4. React.memo() also works with components rendered from the server using ReactDOMServer.

Using React.memo()

With React.memo(), you can now have memoized functional components that bail out of rendering on unnecessary updates using shallow comparison of props.

Using the new React.memo() API, the previous functional component can be wrapped as follows:

import React, { memo } from 'react';

function PercentageStat({ label, score = 0, total = Math.max(1, score) }) {
  return (
    <div>
      <h6>{ label }</h6>
      <span>{ Math.round(score / total * 100) }%</span>
    </div>
  )
}

// Wrap component using `React.memo()`
export default memo(PercentageStat);

Custom bailout condition

The React.memo() API can take a second argument, which is the arePropsEqual() function.

The default behavior of React.memo() is to shallowly compare the component props. However, with the arePropsEqual() function, you can customize the bailout condition for component updates.

The arePropsEqual() function is defined with two parameters: prevProps and nextProps respectively.

The arePropsEqual() function returns true when the props are compared to be equal, thereby preventing the component from re-rendering, and returns false when the props are not equal.

The arePropsEqual() function acts much like the shouldComponentUpdate() lifecycle method in class components but in the reverse manner.

The following code snippet uses a custom bailout condition.

import React, { memo } from 'react';

function PercentageStat({ label, score = 0, total = Math.max(1, score) }) {
  return (
    <div>
      <h6>{ label }</h6>
      <span>{ Math.round(score / total * 100) }%</span>
    </div>
  )
}

function arePropsEqual(prevProps, nextProps) {
  return prevProps.label === nextProps.label; 
}

// Wrap component using `React.memo()` and pass `arePropsEqual`
export default memo(PercentageStat, arePropsEqual);

Conclusion

With the new React.memo() API, you can now enjoy the performance benefits that come with using functional components together with optimizations that come with memoizing the components.

You can start enjoying the new React features by updating to React 16.6.

Enjoy coding…


Plug: LogRocket, a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.