Demystifying Higher-Order Components (HOCs)

Joseph Aje
Jul 22 · 5 min read

The term, ‘Higher-Order components’(usually abbreviated as ‘HOCs’) gets tossed around a lot, and people often tend to misunderstand it. In fact, a good number of React developers don’t exactly know what HOCs are. And that’s okay. Even the official React documentation covers it under the ‘advanced guides’ section.

But is it really that difficult to grasp?

Heck, no.

Matter of fact, chances are you’re already using HOCs in your codebase; you just don’t know it yet.

Let me show you!

But first, what are HOCs?

A HOC is simply a function that takes in a component as an argument and returns an upgraded/enhanced version of it.

You dig?

Why(and how) do we use them?

Glad you asked!

I’ll walk you through. Let’s begin by bootstrapping our app using create-react-app. Run the following command in your terminal:

npx create-react-app hoc-example

Now, let’s open the folder in our text editor(I’m using VS code). Create a folder named ‘components’ under your ‘src’ folder, like so:

Folder structure so far
Folder structure so far

Everything looking nice and sleek? Great! Let’s march on.

Consider the following scenario: We want to render a header to the screen and have the color change whenever we hover over it.

Create a new component under the ‘components’ folder. Let’s call it component1.jsx

You’d see that we defined functions(changeColor and restoreColor) and we attached them to events that occur on the header. Hovering over the header will change the color to pink and taking my cursor out of the header will restore it to its default color(black).

Now, let’s add it to our App(top-level) component:

When we run npm start in our terminal, we should see something like this in our browser

Great!

Now, we want to create another component; it should render a paragraph this time. We also want to change the color to pink when the user presses a mouse button over it and restore the color to default(black) when the user releases the mouse button over it. Let’s call it component2.jsx.

Now, import this in the App(top-level) component

This is the result:

It works! Great.

But is it really?

Let’s find out.

Now, say we want to create yet another component; it should render a button that changes it’s color to pink(yes, I do love pink; thanks for asking) when clicked.

You’ll start to notice that we’re repeating the exact, same logic(hence, writing the exact, same code) in a tonne of different components.

Remember DRY?

Yeah, that software engineering principle that says “Do not Repeat Yourself”.

This goes completely against it.

So what’s an easy solution to this?

HOCs…durrh (I mean, that’s what this article is about)

Let’s create our very own Higher-Order Component, shall we?

Whip up a new component, real quick. It’s going to be returned in a function this time. We’ll call the function withMouseActions. Examine the following code:

As you can see, all the repeated logic has been moved to this HOC and passed as props to the wrapped component. We can now import this HOC in our initial components and delete the redundant code.

Our initial components should now look something like this

A lot cleaner, eh?

Benefit 1: code reusability, as opposed to code repetition.

Now, we can go a step further to simplify our components by turning them into pure, functional components.

Even cleaner! And they’re functional components now, which means easier testability, increased readability, improved performance and decreased coupling; benefits 2,3,4 and 5 😎.

Some of the popular HOCs you’re probably already using without being aware include react-router-dom’s withRouter and react-redux’s connect. Told ya.

Note: Don’t forget to always add {…this.props} in your HOC to pass down the wrapped component’s original props to the new component. See line 27 in the following snippet:

Everything looks good!

Now you might be thinking, “Why didn’t we just reuse the logic by lifting state? i.e. putting our logic(state and methods) in the closest common ancestor component, which, in this case, is our App(top-level) component, and passing it down as props”.

That could work.

See diagram below:

The red node represents the common ancestor to which logic has been lifted, the blue nodes represent the components that need to make use of that logic, and the common ancestor passes logic down through props.

However, what if we had a more complex structure? See diagram below:

Again, the red node represents the common ancestor to which logic has been lifted, the blue nodes represent the components that need to make use of that logic, the yellow nodes represent intermediary components that just so happen to be between the common ancestor and the base component. The white node is sipping tea and minding its business.

These yellow components have to receive logic(in form of props) from the red component, that they’re not in any way making use of.

In very large apps, you’d have to pass unsolicited props through a long chain of components just for them to get to the ones that actually need them.

That’s not very efficient, is it?

That’s why the HOC pattern is best fitted for achieving code reusability.

By using HOCs, we can have our logic in a single place and reuse it throughout the app, without making our components accept props they don’t care about.

Another cool thing we can do is pass in other arguments to the HOCs. Say I wanted to change the default color in the components from black to whatever, I could just pass in the color as an argument to the HOC.

The result:

And it’s a wrap

Read more about it here:

Here’s a link to the repo containing the complete code.

Also, feel free to ask your questions below or tweet at me. I’ll respond as soon as I can.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade