Higher-order components, or HOCs, are a powerful way to add functionality or adjust behavior of arbitrary components. Indeed, libraries often provide HOCs to augment components with functionality or information.
Take react-redux, for example: to connect your component to your store, you use the connect()
HOC. connect()
wraps
your UI component with a container component that handles passing state between the store and the UI component. Or
react-router’s withRouter
HOC, which injects the router functionality and information into your page components
seamlessly.
Glad you asked! The React docs explain how you too can build HOCs. But spend some time in that doc and you’ll soon realize that here there be dragons. When you build a HOC, you’re now on the hook to manage proper communication between parents and children of the wrapped component.
You must:
props
refs
Furthermore, these considerations are interrelated, so you need to make sure to handle them in the right order and hierarchy.
I agree.
You see, recently, I was writing a HOC to provide React 16 Context API functionality to components. I read those docs and hit each of those caveats. I spent a day or two carefully arranging code to handle them elegantly, wrote thorough tests, and patted myself on the back. Then, I moved onto my next ticket and had to do the same thing for a different context.
Like any good software engineer, I am lazy. I hate doing the same thing twice. So, I took what I learned from the first HOC and extracted it into a new open source module.
addhoc
I’m excited to share addhoc
, a new open-source module to vastly simplify building correct HOCs. It takes the pain
out of the process so you can focus on the actual value-add you’re trying to provide.
addhoc
creates HOC functions that automatically:
props
refs
It’s easy. Start by installing addhoc
as a dependency:
# --save is default in the latest npm, and thus optional here
npm install [--save] addhoc
Then, import addhoc
in your code and start building HOCs.
addhoc
is a function that returns a HOC function. To construct your HOC, you pass a callback that acts as the render
function of your top-level component.
Your callback is provided a function parameter that returns the wrapped child that’s initially provided to the HOC. You
can call that callback with an object of props
to add to the wrapped component.
You can also optionally pass in a string name to show when debugging the React component hierarchy.
Let’s take a look at some examples to better illustrate how to use it:
prop
Sometimes, you want to add one or more props
to wrapped components. With addhoc
, you can make a HOC to do this in
one line of code.
import addhoc from 'addhoc';
import MyComponent from './my-component';
// Make the HOC function
const withFooProp = addhoc(getWrappedComponent => getWrappedComponent({ foo: true }), 'WithFooProp');
// Wrap your component using the HOC
const MyComponentWithFoo = withFooProp(MyComponent);
// Rendering a MyComponentWithFoo will create a MyComponent with prop foo = true
// The component hierarchy will look like
// WithFooProp(MyComponent)
// └── MyComponent
Another common HOC scenario is wrapping one component in another. addhoc
makes this simple as well.
import React from 'react';
import addhoc from 'addhoc';
import MyComponent from './my-component';
// Make the HOC function
const withDiv = addhoc(getWrappedComponent =>
<div>
{ getWrappedComponent() }
</div>, 'WithDiv');
// Wrap your component using the HOC
const MyComponentWithDiv = withDiv(MyComponent);
// Rendering a MyComponentWithDiv will render a div that wraps a MyComponent
With the React 16 Context API, consuming a context now requires wrapping components with a Context Consumer node. This is a great use-case for HOCs as they free consumers of your context from having to deal with the Context directly.
import React from 'react';
import addhoc from 'addhoc';
import MyComponent from './my-component';
// Create the Context
const MyContext = React.createContext('DefaultValue');
// Make the HOC function
const withMyContext = addhoc(getWrappedComponent =>
<MyContext.Consumer>
{ value => getWrappedComponent({ value }) }
</MyContext.Consumer>, 'WithMyContext');
// Wrap your component using the HOC
const MyComponentWithMyContext = withMyContext(MyComponent);
// Now, the MyComponentWithMyContext automatically gets a prop called `value` that gets the context
// value passed in from the context.
render() {
return <MyContext.Provider value='ProvidedValue'>
<MyComponentWithMyContext />
</MyContext.Provider>
}
Sometimes, you want to set some values as part of assembling the HOC and have those available in your render function.
You can pass arbitrary parameters after the name
param to addhoc
and they’ll be passed through as additional
parameters to your render function:
import addhoc from 'addhoc';
import MyComponent from './my-component';
// Make the HOC function
const withFooProp = addhoc((getWrappedComponent, extra) => getWrappedComponent({ foo: extra }),
'WithFoo', 'EXTRA');
// Wrap your component using the HOC
const MyComponentWithFoo = withFooProp(MyComponent);
// Rendering a MyComponentWithFoo will get a `foo` prop with value `EXTRA`
With addhoc
, making higher-order components is easy to do right. It manages the ugly parts for you, allowing you to do
what you do best.
Give addhoc
a try the next time you need to build a HOC and let me know how it went. You can find me on Twitter
[@decompiled]. Have another use case? Contributions are always welcome.
Cover photo by Reto Niederhauser on Unsplash