A Quick Intro to React's Higher-Order Components

May 15, 2018 0 Comments

A Quick Intro to React's Higher-Order Components

 

 

Patrick Moriarty

When you pass a component to a function and it returns a new component, it’s called a higher-order component (HOC). If that sounds simple, it is! And your code will be simpler for using them.

Over the course of this post, you’ll see how the higher-order component abstraction will enable your work to be more readable, reusable, and composable.

Some Terms

Before we move forward, let’s define what it is we’re creating a higher-order version of. While you might see these definitions used interchangeably, it’s important to be aware of their differences:

  • Node: an HTML element that has been mounted on the DOM. Your browser is rendering it, and JavaScript can manipulate it.
  • Instance: a runtime instance of a component class. These are represented as JavaScript objects in memory.
  • Element: a bit of markup that describes a node or a would-be instance. In React, these are transpiled to JavaScript objects, which in turn become instances at runtime.
  • Component: an abstraction over an element. They might carry some internal state, and they may behave differently depending on the props they receive. When asked to render, components return elements.
  • Higher Order Component (HOC): an abstraction over a component. When given a component (and perhaps some other parameters), they return a new component.

Diving In

Say we’re about to start work on our app’s user page. We know what our User object looks like, but we haven’t quite decided what kind of authorization we’d like to use. How can we avoid some heartache later when we determine the right way to go? How do we anticipate the possibility of changing our minds in three months when the page is already finished?

We can start with a simple HOC function named withUser. We want this function to wrap around any component we pass to it and provide our User object as a prop.

const withUser = WrappedComponent => { class WithUser extends React.Component { constructor(props) { super(props); this.state = { user: sessionStorage.getItem("user") }; } render() { return <WrappedComponent user={this.state.user} {...this.props} />; } } return WithUser;
};

Explained:

  • Our function, withUser, takes any component as an argument.
  • Inside, we create a WithUser Component class that reads the User object from sessionStorage and adds it to state.
  • The render function returns the WrappedComponent as a new element with a user prop from state.
  • We pass outer this.props to the inner WrappedComponent.

Alternatively, if state is unnecessary, it’s recommended to use a functional HOC:

const withUser = WrappedComponent => { const user = sessionStorage.getItem("user"); return props => <WrappedComponent user={user} {...props} />;
};

Putting HOCs to use

When we want to access the User object on our page, we can call withUser to wrap the page’s component:

const UserPage = props => ( <div class="user-container"> <p>My name is {props.user}!</p> 
</div>
); export default withUser(UserPage);

And that does it! Our withUser function takes a component as an argument and returns a higher order component. Three months from now, if we decide to change things around, we only have to edit our HOC.

In the Wild

If you weren’t familiar with HOCs before, you might have encountered them without realizing it! Some notable examples:

  • react-redux: connect(mapStateToProps, mapDispatchToProps)(UserPage)
  • react-router: withRouter(UserPage)
  • material-ui: withStyles(styles)(UserPage)

Bonus

The compose function from redux allows multiple HOCs to be composed into one. For example:

import { compose } from 'redux';
// ... other imports export default compose( withStyles(styles), withRouter, withUser
)(UserPage);

In this case, our styles, router, and user would all be passed to our UserPage component.

Happy composing! 🚀


Tag cloud