react design patterns: higher order components

2021-10-30

 | 

~4 min read

 | 

714 words

I’ve been working wtih HOCs again after a bit of a hiatus. In the interim, I’d forgotten a lot of what I’d previously learned and learned a few new things too. I wanted to write some of it down to try and cement the concepts (and make them more digestible while I was at it). Without further ado.

What is a Higher Order Component (HOC)?

HOCs are a design pattern common in React.

The React docs define HOCs as “a function that takes a component and returns a new component.”

What do HOCs provide?

Because HOCs are “just functions” that return components, they can be used to extend the functionality of the component. Last year, I wrote wrote about how HOCs can work as a wrapper, but that was a specific example.

More generically, because HOCs are functions that return a component, they are useful for composing functionality.

In that way, the HOC encapsulates a bit of functionality that you may want to reuse (or even simply abstract out of a component).

It’s worth noting that HOCs were particularly popular before hooks were available. This is because there’s a lot of overlap between the two concepts.

Authoring HOCs

The only “rule” for an HOC is that the function takes a component and returns a component.

sample-hoc.ts

function MyHoc(component:
import * as React from "react"

interface OriginalComponentProps {
  onSubmit: (accessToken: string) => void
  onCancel: () => void
}

export function protectComponent<T extends OriginalComponentProps>(
  OriginalComponent: React.FC<T>,
  authorization: Authorization,
) {
  return function ProtectedComponent(props: T) {
    const { onCancel, onSubmit } = props

    const protectedOnCancel = (_event: MouseEvent) => {
      /* do stuff */
      onCancel()
    }
    const protectedOnSubmit = (_event: MouseEvent) => {
      /* do stuff */
      onSubmit()
    }
    return (
      <OriginalComponent
        {...props}
        onCancel={protectedOnCancel}
        onSubmit={protectedOnSubmit}
      />
    )
  }
}

I like this example for a few reason:

  1. It’s easy to see how an HOC can provide additional functionality and wrap around a component. In this case, I’m overriding the original component’s onCancel and onSubmit methods.
  2. The onCancel and onSubmit methods are the only thing that this protectComponent HOC expects of the component that’s being passed in. In this way, the HOC (with the aid of Typescript) can dictate which types of components are suitable to be enhanced.
  3. Though not demonstrated here, it’s also easy to see that we don’t need to return the (enhanced) OriginalComponent, but could return something completely separate. I’ve put together an example in a CodeSandbox.

Using HOCs

When we want to use an HOC, we just need to find a place where we can invoke it. This means that we need a component to pass into the HOC.

If it’s a third party HOC, like Redux’s connect method, then they are often applied at the bottom of a component file to wrap the component defined with additional properties (state and actions).

sample.ts
function mapStateToProps(state) {}
function mapDispatchToProps(dispatch) {}
function MyComponent() {
  /* ... */
}

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

In this paradigm, we define our component, and then export it separately. I tend to not like this approach because it often results in a default export. Of course, this is addressable:

sample.ts

function mapStateToProps(state){}
function mapDispatchToProps(dispatch){}
function MyComponent(){
  /* ... */
}

const enahnced = connect(mapStateToProps, mapDispatchToProps)(MyComponent)
export enhanced;

It’s just not that common to see this.

Alternatively, we may want to conditionally wrap our functions with an HOC. In that case, instead of wrapping the export, we can wrap a component direclty in the body of another component (i.e., a call site). This is not that dissimilar from conditionally rendering components.

For example:

conditionalHOC.ts
const Action = shouldProtect ? protectAction(BasicAction) : BasicAction

Then, when we get into the render function the component (i.e., the return statement):

conditionalHOC.ts
return (
  /*...*/
  <Action onSubmit={/*...*/} />
)

Conclusion

Though Hooks have replaced quite a of the reason for HOCs in React apps since their introduction, it’s worth understanding how you might use them. In particular, it’s still less common (though, not unheard of) to return a component from a hook, so if you find yourself in a situation where you might have to enhance a component by changing what’s returned, that’s a great time to reach for an HOC.



Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!