Higher Order Components

What they are and how to use them

As I have been learning React I have been going back to the documentation Facebook provides. While they do a great job of explaining things, theres one place where I feel they have made it a little too hard for a React beginner to understand. The topic I am talking about is higher order components (HOCs). The first read through of the documentation left me pretty confused because I was looking for an in depth explanation of how it worked. And even after reading many blogs about the subject, I realized they didn’t do a good enough job of showing real life examples that were explained thoroughly. So this is my attempt at explaining this complex concept in a way that some one just starting to learn React can understand.

What is a higher order component?

A HOC is one that takes in a component as an argument and returns a new component. Its that simple. And while this basic idea is simple I felt like it got a little too complex, a little too fast in the documentation. So this HOC essentially wraps the component passed in, and adds functionality or changes how the component works. This new component is normally named like this,

(Component name)With(Action)

ex. CommentListWithSubscription

The action could be anything that your HOCs adds to your base component like logging, subscription, or tracking. The HOCs acts almost as a template, or partial, that abstracts away code that might be repeated in many components within your application.

Why use Higher order components?

HOCs become extremely useful as your codebase begins to grow. They prevent you from having to repeat the same code for lifecycle events or for passing props. This is probably one of the biggest uses for HOCs. The fact that it is possible to make an abstract component that provides the same code to many different components, but also passes extra props is extremely powerful. They are also useful changing how a component renders. By wrapping a component it is possible to add JSX and other data to the render method to change the look of it. It is possible to restrict access to props, too, by having secret props within the HOC. This may be useful when checking if a user is logged in. If they are, then the HOC might pass down more props or change how components get rendered. This would restrict users who are not logged in, from seeing data only meant for users who are logged in.

Example of HOCs

For my example I’m going to use the same exact one that Facebook provides in their documentation, but I’m going to explain it in a more detailed and easy to understand way. Check out the code and I’ll explain it underneath.

class CommentList extends React.Component {

constructor() {

super();

this.handleChange = this.handleChange.bind(this);

this.state = {

comments: DataSource.getComments()

};

}



componentDidMount() {

DataSource.addChangeListener(this.handleChange);

}



componentWillUnmount() {

DataSource.removeChangeListener(this.handleChange);

}



handleChange() {

this.setState({

comments: DataSource.getComments()

});

}



render() {

return (

<div>

{this.state.comments.map((comment) => (

<Comment comment={comment} key={comment.id} />

))}

</div>

);

}

}

This component is a list of comments. It gets it information about the comments it should render from the DataSource. DataSource is just a global variable that holds the data for the app. In order to get the comments from the DataSource a function getComments() is called, which probably involves a fetch. The two lifecycle methods for this component establish a connection to the DataSource to update as the DataSource changes, such as more comments or added or deleted, and remove the connection to the DataSource. The handleChange() function is responsible for updating the state from the DataSource as it changes. The render function just maps through the comments in the state, and creates individual Comment components from the collection. Good so far?? I hope the answer is yes!

class BlogPost extends React.Component {

constructor(props) {

super(props);

this.handleChange = this.handleChange.bind(this);

this.state = {

blogPost: DataSource.getBlogPost(props.id)

};

}



componentDidMount() {

DataSource.addChangeListener(this.handleChange);

}



componentWillUnmount() {

DataSource.removeChangeListener(this.handleChange);

}



handleChange() {

this.setState({

blogPost: DataSource.getBlogPost(this.props.id)

});

}



render() {

return <TextBlock text={this.state.blogPost} />;

}

}

This BlogPost component is responsible for rendering a TextBlock based on the id provided by the props. If you look at it, it looks almost exactly the same as the CommentList. It gets the text for the post by calling the getBlogPost() function on the same global DataSource. It also uses the same lifecycle methods to establish a connection to the DataSource and to update accordingly as it changes. This is handled in the handleChange() function just like it is handled in CommentList. Finally it renders some text for the blog post based on the state.

While these two components are not necessarily related, they both share much of the same code. So how could we make our code DRYer (Don’t Repeat Yourself)? With a HOC of course.

function withSubscription(WrappedComponent, selectData) {

return class extends React.Component {

constructor(props) {

super(props);

this.handleChange = this.handleChange.bind(this);

this.state = {

data: selectData(DataSource, props)

};

}



componentDidMount() {

DataSource.addChangeListener(this.handleChange);

}



componentWillUnmount() {

DataSource.removeChangeListener(this.handleChange);

}



handleChange() {

this.setState({

data: selectData(DataSource, this.props)

});

}



render() {

return <WrappedComponent data={this.state.data} {...this.props} />;

}

};

}

This is a functional component that we could use to abstract away the shared code of the two classes. The first argument in this HOC is the component to be wrapped, and the second is a function that will fetch the correct data for the component to be wrapped. This function passed in is called in the constructor to set the state with the correct data needed. It takes in the global DataSource and any props that the component might take. The HOC is returning an anonymous class. But don’t be afraid! This name will be assigned to the class when this HOC is called and assigned to a variable. As you can see we have made a general container that will take care of the lifecycle methods. We will use the shared handleChange() function to call the data selecting function passed as an argument to the HOC. By just changing what function is passed when this HOC is called, we can customize the data being selected for the specific component. In the render, we pass any props that the WrappedComponent might be taking, using the spread operator. This will split any key value pairs passed as props and will pass them to WrappedComponent. So what does that look like with our two classes from before?

const CommentListWithSubscription = withSubscription(CommentList,

(DataSource) => DataSource.getComments()

);



const BlogPostWithSubscription = withSubscription(BlogPost,

(DataSource, props) => DataSource.getBlogPost(props.id)

);

By passing two different data-getting functions when calling our HOC, we can customize what data each component receives. The CommentList gets an anonymous function that calls our getComments() function. The BlogPost gets an anonymous function that takes the DataSource and props, because the getBlogPost() function requires the id of the blog post. Now we have abstracted away our functions that get our data for our components, and in turn have cutdown our code by not repeating. You can see that we are assigning the name of the brand new component by making it a const functional component. So now, whenever we need a CommentList or BlogPost we can just call on our improved components that are wrapped by WithSubscription. And just because the code in these examples are almost the same between the original components, doesn’t mean they have to be in other situations. For example, if we needed some other data in our CommentList which handled Likes, we could just include that code in our CommentList component. We would still use the HOC to deal with the common code shared between BlogPost and CommentList, but CommentList could still have unique code in it, that BlogPost might not.

I hope this explanation was detailed enough and cleared up any confusion you might have had about HOCs. Now it’s time for you to try making some HOCs!

Sources: