Skip to content
Swizec Teller - a geek with a hatswizec.com

Using HOCs to DRY up your code

Higher order components – HOCs – are one of the best ways to improve your React code. When you see a lot of components sharing similar code, it's time for a HOC.

A HOC used as a decorator can turn a 36-line component like this:

@inject("configStore")
@observer
class Toolbox extends Component {
constructor(props) {
super(props);
autorun(this._render.bind(this));
}
componentDidUpdate() {
this._render();
}
componentDidMount() {
this._render();
}
componentWillUnmount() {
this._cleanup();
}
_init() {
this.backbone = new ToolboxView({
currentUser: new Backbone.Model(toJS(this.props.configStore.currentUser)),
pages: [],
});
}
_render() {
this._cleanup();
this._init();
this.backbone.setElement(this.refs.anchor).render();
}
_cleanup() {
if (this.backbone) {
this.backbone.undelegateEvents();
}
}
render() {
return <div ref="anchor"></div>;
}
}

Into an 11-line beauty like this:

@inject("configStore")
@observer
@backbone(ToolboxView)
class Toolbox extends Component {
backboneProps = {
currentUser: new Backbone.Model(toJS(this.props.configStore.currentUser)),
pages: [],
};
render() {
return this.anchor;
}
}

1282786204310

This is a Backbone integration HOC based on my approach to integrating React, MobX, and Backbone from a few months ago. It takes away all the integration boilerplate and lets you focus on the important stuff – setting data for the old Backbone View and potential rendering embelishments.

The same approach should work with any component-based library like Preact, Inferno, and Vue, but I haven't tried yet.

So how does this work? How did a HOC replace 25 lines of JavaScript with a single @backbone(ToolboxView) decorator? Let me show you.

How HOCses improve your code

The first step is to take everything generic out of your component and put it into a new one. This will be what your HOC does.

If you can't tell what's generic, wait until you have two components that look similar. Any boilerplate they share is a good candidate for a HOC.

In our example that boilerplate is:

  • the constructor that sets up autorun
  • functions keeping Backbone and React render in sync
  • all the Backbone rendering things
  • an anchor element for Backbone to hook into

Soooo… everything. ?

We're leaving React's render() method out of the HOC to give consumers better flexibility, and we're adding the ability to set Backbone params. Those are important.

Step 1: the core of a HOC

The core of our HOC is a React (or Preact or Inferno) component:

class Backbone extends WrappedComponent {
constructor(props) {
super(props);
autorun(this._render.bind(this));
}
componentDidUpdate() {
if (super.componentDidUpdate) {
super.componentDidUpdate();
}
this._render();
}
componentDidMount() {
this._render();
if (super.componentDidMount) {
super.componentDidMount();
}
}
componentWillUnmount() {
if (super.componentWillUnmount) {
super.componentWillUnmount();
}
this._cleanup();
}
_init() {
this.backbone = new BackboneView(
Object.assign({},
this.props,
this.backboneProps
));
}
_render() {
this._cleanup();
this._init();
this.backbone.setElement(this.refs.anchor).render();
}
_cleanup() {
if (this.backbone) {
this.backbone.undelegateEvents();
}
}
get anchor() {
return (<div ref="anchor">)
}
}
</div>

Looks almost the same as our original implementation.

constructor sets up autorun so our component can react to MobX store changes. This automatically turns our Backbone'd components into observers, but I like to include the @observer call when using them anyway. Helps with readability :)

The componentDidMount, componentDidUpdate, and componentWillUnmount methods ensure our Backbone View gets rendered, re-rendered, and cleaned up when appropriate. Each method includes a hook for users of our HOC to add their own lifecycle logic. I haven't really had a use for this yet, but I think it's a good idea to have.

_init, _render, and _cleanup handle the actual Backbone stuff. They're helper methods that make it easier to reuse bits in different React lifecycle methods.

get anchor is like a render() method but not. We could render here, but that would reduce flexibility. Instead, we give consumers an easy way to render the Backbone anchor point wherever.

Step 2: Making it a HOC

To turn this component into a real HOC, we have to wrap it in a function. You could call this function an class factory because that's what it is. It creates classes.

function (WrappedComponent) {
return // code from above
}

Yep, a function that takes a component to be wrapped and returns a class extending said component. That really is it.

Congratulations, you have made a HOC.

Caveat: we use super when to invoke a method on the parent class – the wrapped component – and this when we access instance properties even if they are defined on the parent. Both your wrapped component and your HOC core component share the same this reference.

Step 3: Adding more arguments

Now here's a tricky part. Adding more arguments – like which Backbone View to wrap with – requires currying. At least it does if you want to use your HOC as a decorator like I did.

Currying means that instead of having functions with multiple arguments, we create a composition of single-argument functions. I'm not entirely certain why currying is all the rage, but it makes many approaches to functional programming easier to use.

Our HOC then looks like this:

function backbone(BackboneView) {
return function (WrappedComponent) {
return class Backbone extens WrappedComponent {
// your HOC core
}
}
}

Now the logic inside your HOC can use BackboneView to decide which component to use. This means that you don't need a new HOC for every Backbone View you want to make available in React. Wonderful.

Step 4: Using it

With all that done, your HOC is ready to use. In our case, we use it for rendering old Backbone Views in React code.

Using it looks like this:

@inject("configStore")
@observer
@backbone(ToolboxView)
class Toolbox extends Component {
backboneProps = {
currentUser: new Backbone.Model(toJS(this.props.configStore.currentUser)),
pages: [],
};
render() {
return this.anchor;
}
}

backboneProps lets us define parameters fed into the Backbone View – the HOC accesses them as this.backboneProps.

Inside render(), we use this.anchor anywhere we want to place the anchor point for our Backbone View. In this case we're rendering just that, but in others we might want to wrap it with useful markup that makes it look better in its new context.

Conclusion

HOCs are great, and I wish I started using them earlier. There's definitely going to be a chapter about HOCses in the new version of React+D3. Some prime targets for this stuff in there.

And yes, you can use them without decorators. It's just not as pretty. Something like this:

class MyThing extends Component {
// stuff
}
export backbone(OldView)(MyThing);

Now how would I make them work with functional stateless components? ? Should be just function composition, right?

Did you enjoy this article?

Published on December 16th, 2016 in Front End, react, Technical

Learned something new?
Want to become a high value JavaScript expert?

Here's how it works 👇

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

Start with an interactive cheatsheet 📖

Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career.

"Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

~ Ashish Kumar

Join over 10,000 engineers just like you already improving their JS careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

Ready to Stop copy pasting D3 examples and create data visualizations of your own?  Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️