Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Using HOCs to DRY up Your Code

DZone's Guide to

Using HOCs to DRY up Your Code

In this post we take a look at one way to streamline your React code by making use of HOCs. Not sure what an HOC is? Read on to find out!

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

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 embellishments.

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" />)
    }
}

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?

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
backbone ,react ,javascript ,web dev

Published at DZone with permission of Swizec Teller, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}