Making React HOC functions the easy way with addhoc — GoDaddy Engineering Blog

April 02, 2019 0 Comments

Making React HOC functions the easy way with addhoc — GoDaddy Engineering Blog

 

 

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



Tag cloud