Render Props, Render Callbacks And Higher-Order Components Are All Interchangeable

Making rendering patterns interchangeable

A. Sharif
JavaScript Inside
4 min readJun 12, 2018

--

Introduction

There has been a lot of discussion around concepts like render-props, render-callbacks and higher-order components in the React community. Most commonly mixins where used to enhance a React Component with additional functionality like managing state f.e. After React moved from createClass to ES6 classes, higher-order components became the favoured why to enhance components, but also the render callback pattern was in use, f.e. react-motion leverages children props to pass additional props and functionalities. A less common pattern was to use a so called render prop, so instead of the above mentioned approaches, you define a prop which accepts a callback. Michael Jackson’s “Use a Render Prop!” made this pattern very popular.

Basics

All three mentioned patterns solve the same thing that mixins used to solve. Before we continue, let’s look at a couple of practical examples.

If you understand the two concepts, skip this part and check the actual transformation examples.

Render Prop

First, let’s build a small component that keeps track of our count state and renders any callback we pass to our render prop.

Note: the callback prop can also have a different name than render, the pattern is only called render prop.

Take a look at the following example:

class CounterComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
update = type => {
if (type === "Inc") {
this.setState(({ count }) => ({ count: count + 1 }));
} else if (type === "Dec") {
this.setState(({ count }) => ({ count: count - 1 }));
}
};
render() {
return this.props.render({
...this.state,
...this.props,
update: this.update
});
}
}

In our CounterComponent render function we merge props, state and add an update function and pass it to the render prop.

Next, we will need a component that renders anything valuable to the screen.

const Counter = ({ count, update }) => (
<div>
<button onClick={() => update("Inc")}>Inc</button>
{count}
<button onClick={() => update("Dec")}>Dec</button>
</div>
);

Our Counter component accepts a count and an update function and displays an increment and decrement button as well as the current count.

Finally we can use our CounterComponent to enhance the Counter with the additional functionality.

render(
<CounterComponent render={props => <Counter {...props} />} />,
document.getElementById("root")
);

Higher-Order Components

Now that we covered render props, let’s take a look at how we would construct a higher-order component that offers the exact same functionality.

const withCounter = Component =>
class Hoc extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
update = type => {
if (type === "Inc") {
this.setState(({ count }) => ({ count: count + 1 }));
} else if (type === "Dec") {
this.setState(({ count }) => ({ count: count - 1 }));
}
};
render() {
return <Component
{...this.state}
{...this.props}
update={this.update}
/>;
}
};

Looking at the above the example, we can see that we need to pass in our to be rendered Component to receive a brand new Component with the enhanced functionalities and props.

const CounterExample = withCounter(Counter);render(<CounterExample />, document.getElementById("root"));

We should have covered the basic concepts now. Knowing that we can use different patterns to achieve the same results, we will focus on making theses concepts interchangeable.

Transforming between render props and higher-order components

Sometimes you might have a library that offers a higher-order-component but your favoured way of composing components is via JSX. Sometimes you might have a library that offers a render-prop, but you would like to compose multiple enhancing components via a compose function f.e. The interesting thing is that these concepts are interchangeable.

Let’s build on our previous examples and add transformation functions that would take a higher-order component and return a render-prop and vice versa.

fromHoc: HOC -> RenderProptoHoc: RenderProp -> HOC

The toHoc functionality can be summed as:

toHoc: Render => Comp => props => ( 
<Render {...props} render={props => <Comp {...props} />} />
)

Also check ”Use a Render Prop!” for an alternative implementation.

This converts a render prop to a higher order component.

const withCounter = toHoc(CounterComponent);const CounterExample = withCounter(Counter);

Let’s verify this behaviour.

Converting from a higher-order component to a render-prop is slightly more complicated. We need to pass in a component with a render prop to the actual higher-order-component. All credits to Brent Jackson, (see this implemented in refunk).

fromHoc: hoc => {
class Render extends React.Component {
render() {
return this.props.children(this.props);
}
}
return hoc(Render);
}

Alternatively, this can also be accomplished without using a class baed approach. All credits to Rodrigo Pombo (also check this example).

fromHoc: hoc => hoc(props => props.render(props));

Again, we can verify that this works as expected.

We can build a small helper that converts back and forth between higher-order-components and render-props. Check the following example. It should also be noted that we can initiate the toHoc with an additional render name, as the rendering prop might have a different name or be a child prop.

const iso = {
fromHoc: hoc => hoc(props => props.render(props)),
toHoc: Render => Comp => props => (
<Render {...props} render={props => <Comp {...props} />} />
)
};

Finally, let’s see the different concepts back to back.

Summary

Render props, render callbacks and higher-order component are interchangeable. For most cases render props will suffice, but you can transform back between these patterns in user land if needed.

Very special thanks to Brent Jackson Kent C. Dodds and Rodrigo Pombo for sorting out problems with the fromHoc implementation.

Links

Use a Render Prop!: render prop pattern explained by Michael Jackson

React Patterns: excellent collection of React patterns by michael chan

Higher-Order Components: Official React Docs

Render Props: Official React Docs

Downshift: Example of using a render prop via Downshift by Kent C. Dodds

Refunk: Example of converting a higher-order component to render prop by Brent Jackson

If there are better ways to solve these problems or if you have any questions, please leave a comment here or on Twitter.

--

--

A. Sharif
JavaScript Inside

Focusing on quality. Software Development. Product Management.