Making React HOC functions the easy way with addhoc

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.

Sounds cool! How do I get in on this?

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:

Furthermore, these considerations are interrelated, so you need to make sure to handle them in the right order and hierarchy.

Ew, this sounds ugly…

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.

Avoid building HOCs ad hoc with 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:

Nice, how do I use it?

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:

Example 1: Adding a 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

Example 2: Wrapping in another component

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

Example 3: React 16 Context consumer

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>
}

Example 4: Passing through configuration

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`

Conclusion

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 or in the GoDaddy OpenSource Slack channel. Have another use case? Contributions are always welcome.

Cover photo by Reto Niederhauser on Unsplash


Author