Reusing logic in your React apps with Higher-Order Components
As you build React applications, you will run into situations where you want to share some functionality across multiple components. It could be that you you want a component to display a loading indicator, while it waits for some data to become available. Or perhaps, you need to access logged in user state from multiple components in your app. There’s got to be a way to share this state across those components, without having to basically copy-paste logic around, right? Enter Higher-Order Components (HOCs).
This post is a look at HOCs and how they make it easy to write easy-to-reason-about, reusable code. I hope to leave you with a good understanding of how HOCs work and how to start using them in your own code. Let’s get started!
What is a Higher-Order Component?
According to the React docs, a higher-order component is a function that takes a component and returns a component. Put differently, a HOC will basically take some component as an argument, (let’s call this the passed-in component from now on) and return a new component (let’s call this the enhanced component) that essentially wraps the passed-in component, with logic or functionality (via props), that it wouldn’t have had access to otherwise.
So whenever we want a component to access some reusable logic, we run that component through a HOC and render new component that gets returned.
Let’s consider a simple example to drive the point home. Say we want to be able to access the current date from several of the components in our app. Well, we could write const currentDate = new Date()
in any component that needs it. But instead, let’s see how using a HOC would nicely abstract that logic away and make it available on-demand to any component that needs it.
We’ll call this HOC withCurrentDate
. Here’s the code for it:
Let’s take a moment to understand what’s happening here. The withCurrentDate
function is a JavaScript function like any other. It takes PassedComponent
as an argument and returns a new component. This new component passes currentDate
along with any other props it receives downPassedComponent.
Okay, let’s move on.
With our withCurrentDate
HOC defined, we can now use it, to make any component currentDate
-aware, by simply passing the component as the argument to our HOC like so:
A more practical Higher-Order Component
The example above — while useful in demonstrating how a HOC works — , is a bit contrived. Let’s look at a more practical example. Say we have two components. The first — ContactList
— renders a loading indicator, while it waits for contacts to be loaded from the randomuser.me API and renders list of contacts when the data arrives:
The second — GithubUserList
— also renders a loading indicator, while it waits for user data to be loaded from Github’s API and renders list of users when the data arrives:
ContactList
and GithubUserList
aren’t identical — they load data from different endpoints, and they render different output. But much of their implementation is the same:
- On mount, make a call the remote endpoint for data
- Render loading text, while we wait
- Call
setState
with the results, when the data comes back - Render a list of items with the retrieved data
You can imagine that in a large app, this same pattern of showing some loading text while we wait for data to become available will occur over and over again. What we want is an abstraction that allows us to define this logic in a single place and share it across many components. This is where higher-order components excel.
Let’s write a function that creates components, like ContactList
and GithubUserList
, that show a spinner while they make a request to a remote source for data and render that data when it becomes available. This function (which is our HOC) will accept as one of its arguments a child component that will receive the data once it arrives. Let’s call the function withLoader
:
The interesting thing to note here, is that because we want withLoader
itself to be reusable, we want to keep it as generic as possible. In other words, withLoader
shouldn’t concern itself with how and from where the data is fetched. It is for this reason, that withLoader
accepts a second argument:loadData
, that handles the details of making the necessary API calls. This allows with loader to simply call fetchData
and wait for it to return some data with which, which it then uses to update its local state.
With the introduction of withLoader
we can now re-write ourContactList
and GithubUserList
as presentational components (i.e. simple components that simply accept some props and return some UI), since all the logic to deal with network calls, loading spinners etc has been nicely abstracted away. Plus we can reuse it anywhere we need that functionality.
Having been freed of the burden of dealing with network calls and loading spinners etc, our ContactList
and GithubUserList
components can now be re-implemented as simple presentational components — functional components that simply accept some props and return some ui.
Take a moment to compare these versions with the ones we started out with. That’s pretty damn sweet, if you ask me. Maintaining components like these is pure bliss.
Nice, but how do we actually use the withLoader
component? Check it out:
We just call withLoader
with the component that needs functionality — which in this caseContactList
and a function to fetch the data we need — fetchRandomUsers
in this case. What we get in return, is a new component — ContactListWithLoader
that has all the functionality we need. Now, instead of rendering ContactList
directly, we’ll simply render ContactListWithLoader
and boom — done.
You can see the withLoader
HOC in action here.
Thanks to higher-order components, we can enhance our simple functional components with whatever logic we want. This is the power of the HOC pattern. We can manipulate props or maintain internal state outside of our presentational components.
A few caveats to look out for
Higher-order components come with a few caveats that aren’t immediately obvious if you’re new to React.
1 — Don’t Use HOCs Inside the render Method
React’s uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render
is identical (===
) to the component from the previous render, React goes the update route. If they’re not equal, the previous subtree is completely unmounted (and remounted).
You normally shouldn’t need to think about this, but in the case of HOCs, it matters because it means you can’t apply a HOC to a component inside the render method of a component, since you’ll be effectively creating a completely new component every time. What to do instead?
Better to apply HOCs outside the component definition so that the resulting component is created only once. This way, its identity will be consistent across renders. If you need to apply a HOC dynamically, you can also do it inside a component’s lifecycle methods or its constructor to achieve the same effect of preserving the component’s identity across renders.
2 — Static Methods Must Be Copied Over
If you have defined any static methods on your React component, the new container component that gets returned when you apply a HOC will not have any of the static methods of your original component.
To solve this, you could copy the methods onto the container before returning it like so:
However, this approach requires you to know exactly which methods need to be copied. You can use hoist-non-react-statics to automatically copy all non-React static methods:
3 — Refs Aren’t Passed Through
The key thing to note here is, if you pass a ref to a HOC it will not get passed through to the wrapped component. The ref will refer to the outermost container component, instead of the the wrapped component. This is because when React encounters a ref
attribute on a component, it attaches the ref to the HTML element or component on which the attribute was set. This is not what we want. Fortunately, we can explicitly forward refs to the wrapped component using the React.forwardRef
API. Learn more about how to do this in this post on using refs.
Interesting Aside: Usage out in the wild
I thought I’d take a moment to highlight out a good example of HOCs being used out there on the wild.
Redux, for example, uses a higher-order component returned by its, connect
function to pass values from your application store to your components. It also does some error checking and component lifecycle optimisations that, if done manually would cause you to write a ton of boilerplate code. connect
's typical signature looks like so:
It might not be immediately, obvious but connect
is actually just a higher-order function that returns a higher-order component! If you break it apart, it’s easier to see what’s going on.
Conclusion
By now, you hopefully have good understanding of HOCs — the situations where they really come in handy and how you might use them to share functionality and simplify things in your projects.
Higher-Order components are a very useful pattern in React. When used correctly, they can lead us to write significantly less code, and components that are easy to reason about and maintain.
Congratulations! You made it to the end. I had a lot of fun writing this article. If you find out useful, share it with someone who might too. Cheers!