erikras.com
HomeAbout

The HOC Drill Pattern

Drilling props through HOC-wrapped components

Posted in Coding, Redux, React
April 27, 2016 - 3 min read
The HOC Drill Pattern

With the rise of Higher Order Component composition in the React community, there are a plethora of libraries, such as my own redux-form, that will add functionality to your application by decorating your component and wrapping it in a HOC.

What you end up with is a component structure like this, where you control the yellow components, MyContainer and MyWrappedComponent, but you have no control over the red code, LibraryHOC.

Wrapped Components Diagram

Most such libraries are polite enough to pass through any props that they do not use as parameters down to your wrapped component, but sometimes they will not provide a way, e.g. getWrappedInstance, to access instance methods on your wrapped component.

What can you do? How can you get through this barrier?

Concrete Example

Let’s lay out a concrete example. Your MyWrappedComponent has instance methods of load(), save(), and clear(), and you need buttons in your MyContainer to be able to call these.

// MyContainer.js
import React, { Component } from "react";
import MyWrappedComponent from "./MyWrappedComponent";
export default class MyContainer extends Component {
constructor() {
// bind handlers
this.handleLoad = this.handleLoad.bind(this);
this.handleSave = this.handleSave.bind(this);
this.handleClear = this.handleClear.bind(this);
}
handleLoad() {
// somehow call load() on MyWrappedComponent
}
handleSave() {
// somehow call save() on MyWrappedComponent
}
handleClear() {
// somehow call clear() on MyWrappedComponent
}
render() {
return (
<div>
<button onClick={this.handleLoad}>Load</button>
<button onClick={this.handleSave}>Save</button>
<button onClick={this.handleClear}>Clear</button>
<MyWrappedComponent />
</div>
);
}
}
// MyWrappedComponent.js
import React, { Component } from "react";
import { hocLibrary } from "some-hoc-library";
@hocLibrary({ config: "parameters" })
export default class MyWrappedComponent extends Component {
load() {
// load something
}
save() {
// save something
}
clear() {
// clear something
}
render() {
return <div>Gorgeous UI</div>;
}
}

How can MyContainer drill through the HOC to call methods on MyWrappedComponent?

Wrapped Components Diagram
This HOC is tough!

Callback Drill

Because our HOC allows us to pass props down through it, we can pass a callback that will give our container component references to the wrapped component’s instance methods.

Let’s see that in action. When the inner MyWrappedComponent is going to be mounted, it sends up the API that it wants to expose to MyContainer.

// MyContainer.js
import React, { Component } from "react";
import MyWrappedComponent from "./MyWrappedComponent";
export default class MyContainer extends Component {
constructor() {
// bind handlers
this.handleCallback = this.handleCallback.bind(this);
}
handleCallback(load, save, clear) {
// save instance methods from MyWrappedComponent to this
this.handleLoad = load;
this.handleSave = save;
this.handleClear = clear;
}
render() {
return (
<div>
<button onClick={this.handleLoad}>Load</button>
<button onClick={this.handleSave}>Save</button>
<button onClick={this.handleClear}>Clear</button>
<MyWrappedComponent methodCallback={this.handleCallback} />
</div>
);
}
}
// MyWrappedComponent.js
import React, { Component } from "react";
import { hocLibrary } from "some-hoc-library";
@hocLibrary({ config: "parameters" })
export default class MyWrappedComponent extends Component {
componentWillMount() {
this.props.methodCallback(
this.load.bind(this),
this.save.bind(this),
this.clear.bind(this)
);
}
load() {
// load something
}
save() {
// save something
}
clear() {
// clear something
}
render() {
return <div>Gorgeous UI</div>;
}
}

Conclusion

It is a little messy, but breaking through barriers often is. I have used this pattern on several occasions, and it works well. One important side note: If your wrapped component might be unmounted while your container remains, to prevent a memory leak, you should “unregister” the instance methods via another callback in componentWillUnmount().

Happy coding!

Discuss on Twitter

© 2023 Erik Rasmussen