React — Composing Higher-Order Components (HOCs)
To put it simply, a higher-order component is a function, that takes a component and returns a new component. I like to think of them as parameterized components. Many times I find myself creating several components with very similar logic, with only 1 or 2 changes. Once I find a use case like this, it’s very simple to abstract the shared logic, and put the logic that changes into parameters.
You can read more about HOCs here, in the official React docs. Since components are just functions and HOCs are just functions that return other functions, we can use functional concepts to chain them using utilities methods such as
compose, which is provided by many libraries (it's included in Redux!).
I’m going to talk about a few common use cases, then go over their implementation, and a few ways they can be composed together to make even more powerful HOCs!
Follow Along (Example App)
You can following along with a simple React app I create to demonstrate the following concepts and use cases. It can be used in browser or cloned from the repository.
Live sandbox: codesandbox.io/s/github/caseymorrisus/composing-hocs
GitHub repo: github.com/caseymorrisus/composing-hocs
Now that we know a little about higher-order components, we can go over a few common use cases.
It’s common to pull data from an API and display a “spinner” or message until the data has been fetched. You may have a standard
Loading component which has a prop
message for displaying a loading message specific to your current application.
Almost identical to the Loader HOC, but instead displays an
Error component and expects an
error prop. Can be supplied a custom
It’s good practice to display some type of message if data was successfully fetched, but none of the given type were returned from the data source. For example, instead of display a blank list of posts, if no posts have been created, you could have a
DefaultPost component which could prompt the user to create a post.
Container components frequently fetch data and pass that data as a prop to a child component. Generally the only thing that changes between these container components is the API endpoint. We can also use HOCs to easily pass in mock data for development purposes.
Used for development purposes to quickly prototype components before an API endpoint has been created or implemented within your React project. Data and a delay (to mock async data fetching) are supplied to the HOC. Data is passed down to the wrapped component.
Easily log all prop changes (on render) for a given component, helpful for quick debugging purposes.
Sometimes you need to inject props into a component, this is especially useful when using a branch HOC and only want a single branch to receive specific props.
Adding a timeout that controls state within React will throw an error if a component unmounts before the function supplied to the timeout has fired. We can create an HOC that handles and shares functionality to easily add and clear timeouts.
It’s fairly common to have a container component, which fetches data, and renders a list of components using that data. We can combine this with other HOCs such as:
hasDefault to extend functionality further.
As stated above, it’s fairly common to take an array of data and render a component for each item in the array. This HOC will also spread the current data item as props into the supplied component.
Now that we’ve gone over a few use cases, lets look at some simple implementations before we get into any complex chaining.
Takes a single parameter,
injectedProps and returns a wrapped component with the supplied
injectedProps . Useful when you need to inject props to a single component within the
branch hoc, as the props supplied are injected into both passing and failing components.
Use with branch HOC:
Takes a single parameter, prefix which prefixes the logged message, defaults to an empty string. Logs props to the console on every render of the
Takes 3 parameters:
ComponentOnFail and returns either of the supplied components depending on the result of the
test function. If the
test function returns true,
ComponentOnPass will be returned, if false,
ComponentOnFail will be returned. Immediately returns a new component unlike other HOCs in this post.
Takes 2 parameters:
delay and returns a wrapped component with a new
mockData is injected into the
data prop after the
delay has passed. Can set
delay to 0 or leave blank to have no delay. Uses the
hasTimeouts HOC to extends functionality to include
This higher-order component has no parameters. It returns a wrapped component with new
loadingMessage props expected. It conditionally renders the supplied component if
false. Otherwise, it renders the
Loading component with the
Takes 1 parameter:
ErrorComponent which has a default value of the
Error component. Can supply a custom
ErrorComponent if desired rather than using the default value. It returns a wrapped component with a new expected prop:
hasError. It conditionally renders the supplied component if
hasError is false. Otherwise, it renders the
Uses a default
Error component if none is supplied:
Takes 1 parameter:
Default which expects to be a React component. It returns a wrapped component with a new expected prop:
useDefault. It conditionally renders the supplied component if
useDefault is false. Otherwise, it renders the
Expects a single parameter object which expects properties of
url dictates which endpoint is hit,
params injects URL parameters for the request, and
loadingMessage allows you to customize the loading message within the
Loader component. Uses Axios to get data from an API endpoint. Axios can easily be replaced with your fetch implementation of choice. Injects
loadingMessage as props to be used in the wrapped component.
Expects no parameters and returns a wrapped component with injected props
clearTimeouts. These props allow you to easily create timeouts and be able to clear them when a component unmounts. If you have an active timeout and unmount a component, it will throw an error.
Expects a single parameter
type which dictates the className used in the wrapped div for the list. This HOC expects the list component to have a
data prop which contains an array of items. All properties of individual items are injected as props to the individual wrapped component.
Composition Using Higher-Order Components
The true power of HOCs comes when you start to chain (compose) them. You can chain as many of them as you like. If you have a component which needs an error and default state, simply compose it like so:
The returned component can now be controlled to show an error or a default using its props. In the case of the above example, if
hasError are both
hasError and the
ErrorComponent would take precedent since it is first within the compose function.
Do you want to add logging to the above component and also supply it with mockData? Easy! Simply change the above to:
HOCs can easily be added and composed to extend the functionality of any component!
Expects a single parameter object with props
data is expected to be the param object expected by the
Error expects a custom error component, but if left blank will display the default error component.
DefaultComponent expects a component to display in the container list if no items were fetched from the API.
Quick Note About Debugging
For debugging purposes, we are making our code a bit more verbose. Using the
hasProps HOC as an example, we could further simplify it like so:
The problem with this is that when viewed in the React Developer Tools, the component comes back as
Unknown as it was never given a name. Using the original example provided for the
hasProp HOC would display a
HasProps component within the React Developer Tools.
Using Redux Compose
As mentioned above, Redux supplies a compose method that can be imported and used like so:
You can read more about their implementation over at their documentation here. The compose function isn’t very special in itself, it can be imported from many libraries.
The provided example app shows use of all concepts above. The home page includes components that have been extended with HOCs and also includes buttons to control the props that control the HOCs used to extend them. To put it simply, you’re able to click a button and switch which component renders based on props. This page includes a
MockPostContainer which extends the
Post component with a logger, mock data, loader, error, default, and list.
Posts, Todos, Comments, and Users Pages
All of these pages consume real API endpoints from https://jsonplaceholder.typicode.com/ . They each extend a single component (Post, Todo, Comment, User) with the
I hope the above patterns / components help you create some new higher-order components of your own. These are the most common use cases for me, I’m sure you probably have a few common use cases not listed above. I’d love to hear how higher-order components have helped you simplify your code!
If you appreciated the article and it helped in any way, don’t hesitate to share and clap! 👏