Higher-Order Components

In React, higher-order components (HOCs) are a pattern for reusing component logic. Ultimately, higher order components allow you to dynamically generate wrapper components.

Before React existed, developers leveraged JavaScript’s support for functions as first-class objects, meaning that functions can be treated just like any other data type (i.e. stored in a variable, object, or array, passed as an argument to a function, or returned from a function). Understanding what higher-order functions are and how they work will help you to understand and use higher-order components in React.

When you finish this article, you should be able to:

Reviewing higher-order functions

Functions that operate on other functions, either by receiving them as arguments or returning them, are called higher-order functions. Said differently, higher-order functions are functions that:

Using closures with higher-order functions

A closure, also known as lexical scoping, is a function that uses free variables, variables defined outside of its scope. Closures come in handy when writing higher-order functions. Consider the following code:

const calculator = function (operationCb) { // high-order function
  return function (op1, op2) { // closure
    console.log(`calling with ${op1} ${op2}`);
    const result = operationCb(op1, op2);
    console.log(`equals ${result}`);
  };
}

const addition = function (n1, n2) { // callback
  console.log(`${n1} + ${n2}`);
  return n1 + n2;
}

const adder = calculator(addition);
adder(1, 2);
// calling with 1 2
// 1 + 2
// equals 3

The calculator function receives a callback as an argument (operationCb) which is called in the anonymous function calculator returns. This return value would not work if the inner function could not close over operationCb, a variable defined outside of its scope.

Defining higher-order functions with arrow functions

Arrow functions make it easy to write higher-order functions. The two examples below illustrate the same function:

// Without arrow functions (ES5):
function foo(arg1) {
  return function(arg2) {
    return function(arg3) {
      console.log(`${arg1} came before ${arg2} and ${arg3} came last`);
    };
  };
}

// With arrow functions (ES6):
const foo = arg1 => arg2 => arg3 => {
  console.log(`${arg1} came before ${arg2} and ${arg3} came last`);
};

Here’s the earlier calculator function rewritten using arrow functions:

const calculator = (operationCb) => (op1, op2) => {
  console.log(`calling with ${op1} ${op2}`);
  const result = operationCb(op1, op2);
  console.log(`equals ${result}`);
};

Note: Remember, ES6 arrow functions, unlike normal JavaScript functions, are automatically bound to the context (this) that existed when they were defined. In other words, this means the same thing inside an arrow function that it does outside of it.

Leveraging higher-order components (HOCs)

In the same way that higher-order functions can receive a function as an argument and return a new function, higher-order components or HOCs receive a React component as an argument and return a new component.

ProtectedRoute and AuthRoute

In the React Twitter Lite project, you created two HOCs to control what pages users could see based upon their authentication status. The ProtectedRoute HOC ensured that only logged users could view the Profile and Home pages while the AuthRoute HOC prevented logged in users from viewing the Login or Registration pages:

export const ProtectedRoute = ({ component: Component, path, currentUserId, exact }) => {
  return (
    <Route
      path={path}
      exact={exact}
      render={(props) =>
        currentUserId ? <Component {...props} /> : <Redirect to="/login" />
      }
    />
  );
};
export const AuthRoute = ({ component: Component, path, currentUserId, exact }) => {
  return (
    <Route
      path={path}
      exact={exact}
      render={(props) =>
        currentUserId ? <Redirect to="/" /> : <Component {...props} />
      }
    />
  );
};

Notice how both components accept a component via the component parameter and return a Route component (that renders the passed component via a render prop).

Using HOCs to keep code DRY

HOCs give React developers a powerful way to reuse component logic so that they can keep their code DRY.

When learning about Redux container components, you might have noticed that the FruitManagerContainer and FarmerManagerContainer components in the Fruit Stand application contain the same componentDidMount and componentWillUnmount lifecycle method implementations:

// ./src/components/FruitManagerContainer.js

import React from 'react';
import store from '../store';
import {
  addFruit,
  addFruits,
  sellFruit,
  sellOut,
} from '../actions/fruitActions';
import FruitManager from './FruitManager';

class FruitManagerContainer extends React.Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  add = (fruit) => {
    store.dispatch(addFruit(fruit));
  }

  addBulk = (fruit) => {
    store.dispatch(addFruits(fruit));
  }

  sell = (fruit) => {
    store.dispatch(sellFruit(fruit));
  }

  sellAll = () => {
    store.dispatch(sellOut());
  }

  render() {
    const { fruit } = store.getState();
    const distinctFruit = Array.from(new Set(fruit)).sort();

    return (
      <FruitManager
        fruit={fruit}
        distinctFruit={distinctFruit}
        add={this.add}
        addBulk={this.addBulk}
        sell={this.sell}
        sellAll={this.sellAll} />
    );
  }
}

export default FruitManagerContainer;
// ./src/components/FarmerManagerContainer.js

import React from 'react';
import store from '../store';
import { hireFarmer, payFarmer } from '../actions/farmersActions';
import FarmerManager from './FarmerManager';

class FarmerManagerContainer extends React.Component {
  componentDidMount() {
    this.unsubscribe = store.subscribe(() => {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  pay = (id) => {
    store.dispatch(payFarmer(id));
  }

  hire = (name) => {
    store.dispatch(hireFarmer(name));
  }

  render() {
    const { farmers: farmersState } = store.getState();
    const farmers = Object.values(farmersState);

    return (
      <FarmerManager
        farmers={farmers}
        pay={this.pay}
        hire={this.hire} />
    );
  }
}

export default FarmerManagerContainer;

While the amount of duplicated code is currently relatively small, keep in mind that the approach of using the forceUpdate method to render the component whenever state is updated in the store isn’t optimal. Calling forceUpdate causes render to be called without first calling shouldComponentUpdate.

Ideally, each container component should contain logic to determine if the state that it retrieves from the store has actually changed before continuing with rendering. Adding the code for this logic would increase the amount of code duplicated in each container component.

React-Redux connect

React-Redux, a library from the creators of Redux, includes a higher-order component named connect that you can use to create container components. Using connect frees you from having to manually create the FruitManagerContainer and FarmerManagerContainer container components. Using connect also eliminates boilerplate code so you can focus on what makes each container unique: selecting specific slices of state from the store and writing functions to dispatch specific actions. Even better, connect includes the logic that’s needed to optimize the rendering performance, only rendering when the state retrieved from the store has actually changed.

Over the next couple of articles, you’ll learn how to use connect and the React-Redux library.

Writing a HOC for creating container components

Higher-order components like React-Redux connect, while extremely useful, can be confusing to understand. To help give you some insight into the underlying implementation of the connect function, you can build a your own custom version of connect (albeit without the logic that’s needed to optimize rendering performance).

Don’t worry if you struggle to fully understand the following custom version of connect. It takes time and practice to get comfortable with the techniques that are used to create higher-order components. And remember, you don’t have to build your connect function! Going forward, you’ll use the well-maintained, highly optimized connect function provided by the React-Redux library.

Consider the following custom connect function:

// ./src/connect.js

import React from 'react';
import store from './store';

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    render() {
      let stateProps = {};
      if (mapStateToProps) {
        stateProps = mapStateToProps(store.getState(), this.props);
      }

      let dispatchProps = {};
      if (mapDispatchToProps) {
        dispatchProps = mapDispatchToProps(store.dispatch, this.props);
      }

      const propsToSpread = Object.assign({}, this.props, stateProps, dispatchProps);

      return (
        <WrappedComponent {...propsToSpread} />
      );
    }

    componentDidMount() {
      this.unsubscribe = store.subscribe(() => {
        this.forceUpdate();
      });
    }
  
    componentWillUnmount() {
      if (this.unsubscribe) {
        this.unsubscribe();
      }
    }
  }
);

export default connect;

The connect function is a higher-order component that returns a function. That function returns an anonymous class component that wraps the component passed in via the WrappedComponent parameter.

The anonymous class component is a “generic” container component that connects WrappedComponent to the Redux store by:

Comparing nested arrow functions to regular functions

As you saw earlier in this article, higher-order functions can be written using arrow functions or regular functions. The connect higher-order function can be written using arrow functions and implicit return statements:

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    // Code removed for brevity.
  }
);

Or can be rewritten using regular functions and explicit return statements:

function connect(mapStateToProps, mapDispatchToProps) {
  return function(WrappedComponent) {
    return class extends React.Component {
      // Code removed for brevity.
    };
  };
};

The arrow function syntax, while concise, can be confusing to read.

Dynamically setting component attributes

The connect function’s mapStateToProps and mapDispatchToProps parameters are both functions that are called within the anonymous class’ render method:

const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => (
  class extends React.Component {
    render() {
      let stateProps = {};
      if (mapStateToProps) {
        stateProps = mapStateToProps(store.getState(), this.props);
      }

      let dispatchProps = {};
      if (mapDispatchToProps) {
        dispatchProps = mapDispatchToProps(store.dispatch, this.props);
      }

      // Code removed for brevity.
    }

    // Code removed for brevity.
  }
);

The Redux store’s current state, retrieved using store.getState, is passed to the mapStateToProps function and the store’s dispatch method is passed to the mapDispatchToProps function. The mapStateToProps and mapDispatchToProps functions also receive the anonymous class component’s props (via this.props).

But what are the mapStateToProps and mapDispatchToProps functions exactly? To answer that question, take a look at an example of using the connect function to define the FruitManagerContainer component:

// ./src/components/FruitManagerContainer.js

import connect from '../connect';
import {
  addFruit,
  addFruits,
  sellFruit,
  sellOut,
} from '../actions/fruitActions';
import FruitManager from './FruitManager';

const mapStateToProps = (state) => ({
  fruit: state.fruit,
  distinctFruit: Array.from(new Set(state.fruit)).sort(),
});

const mapDispatchToProps = (dispatch) => ({
  add: (fruit) => dispatch(addFruit(fruit)),
  addBulk: (fruit) => dispatch(addFruits(fruit)),
  sell: (fruit) => dispatch(sellFruit(fruit)),
  sellAll: () => dispatch(sellOut()),
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(FruitManager);

mapStateToProps is a function that accepts store’s current state and returns an object that maps state data to one or more properties. mapDispatchToProps is also a function, but it accepts the store’s dispatch method and returns an object whose properties are set to functions that can be called dispatch actions to the store.

In the connect function, Object.assign is then used to combine the stateProps and dispatchProps objects with this.props (the anonymous class component’s props):

const propsToSpread = Object.assign({}, this.props, stateProps, dispatchProps);

Then the properties defined on the propsToSpread object become props on WrappedComponent using JSX spread attribute syntax (...):

return (
  <WrappedComponent {...propsToSpread} />
);

Within the FruitManager component (the component that the connect function’s WrappedComponent is wrapping), parameter destructuring is used to reference the props provided by the mapStateToProps and mapDispatchToProps functions. These props are then passed as props to the appropriate child presentational component:

// ./src/components/FruitManager.js

import React from 'react';
import FruitList from './FruitList';
import FruitSeller from './FruitSeller';
import FruitQuickAdd from './FruitQuickAdd';
import FruitBulkAdd from './FruitBulkAdd';

const FruitManager = ({ fruit, distinctFruit, add, addBulk, sell, sellAll }) => {
  return (
    <div>
      <h2>Available Fruit</h2>
      <FruitList fruit={fruit} />
      <h2>Fruit Inventory Manager</h2>
      <FruitSeller distinctFruit={distinctFruit} sell={sell} sellAll={sellAll} />
      <FruitQuickAdd add={add} />
      <FruitBulkAdd addBulk={addBulk} />
    </div>
  );
};

export default FruitManager;

Visualizing the flow of data all the from the mapStateToProps and mapDispatchToProps function definitions within the FruitManagerContainer.js file through the connect higher-order component down to the FruitManager wrapped component is difficult to do. Understanding how to use React’s ability to dynamically set component attributes using JSX spread attribute syntax is one of the more challenging aspects of writing higher-order components.

Just remember that you don’t need to fully understand the above example to use the connect function provided by the React-Redux library.

Reviewing a completed Fruit Stand example

To review and run a completed Fruit Stand example application that utilizes the above custom connect higher-order component to create container components, clone the redux-fruit-stand-examples repo.

After cloning the repo, open a terminal and browse to the fruit-stand-redux-with-react-generic-container folder. Run the command npm install to install the project’s dependencies. Then use the command npm start to run the Fruit Stand application.

This Fruit Stand example application is a React application created by the Create React App tooling. When running the application using npm start, the application should automatically open in your default browser. If it doesn’t, you can manually browse to http://localhost:3000/ to view the application.

What you learned

In this article, you learned about higher-order components (HOCs) including how to write a higher-order component that accepts a component as an argument and returns a new component.