I’m Breaking up with Higher Order Components.

October 03, 2017 0 Comments

I’m Breaking up with Higher Order Components.

 

 

Yes, I’m breaking up with higher order components in React. Mostly.

I’d been seeing this other composition pattern on the side for a while. We met initially in some libraries like React Motion and the latest version of React Router. She was very declarative and straight forward. I could read her easily and felt comfortable telling her what I wanted. Not to mention, she was dynamic and flexible, too!

Higher order components and I had a good time. We’re still friends and occasionally connect. But, in the end, I just ended up in a different place. It wasn’t her, it was me.

Ok, ok. Let’s put the relationship metaphor to the side, as I likely couldn’t keep that up anyway. I just want to share this pattern with you and show a few ways in which it can be extremely useful in designing components in React.

This pattern is referred to by a number of different names:

  • render prop
  • render callback
  • function as child

There is no difference between them from a functional standpoint. The concept is simply passing a prop which has a function as its value. That function allows the two components to share data in a decoupled way, allowing the function to render whatever it likes.

Here’s a quick example, where we pass the render function as the children prop to the component.

// Contrived example for simplicity
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Caffeinate extends Component {
propTypes = { children: PropTypes.func.isRequired };
  state = { coffee: "Americano" };
  render() {
return this.props.children(this.state.coffee);
}
}

And we could use it like so,

render(
<Caffeinate>
{(beverage) => <div>Drinking an {beverage}.</div>}
</Caffeinate>,
document.querySelector("#root")
);
//=> Drinking an Americano.

Our Caffeinate component takes a function as the value for the children prop and will then return the result of calling that function when it renders. In this case, we’re passing a piece of state to that child function. When the child function executes, it can name that state something useful and then use it in any way it pleases.

We could have also used an explicit prop other than children to accomplish the same thing:

class Caffeinate extends Component {
propTypes = { render: PropTypes.func.isRequired };
  state = { coffee: "Americano" };
  render() {
return this.props.render(this.state.coffee);
}
}
render(
<Caffeinate
render={
(beverage) => <div>Drinking an {beverage}.</div>
}
/>,
document.querySelector("#root")
);
//=> Drinking an Americano.

This is a pattern I find useful in a few areas, a few of which I discuss below:

We want the bulk of our components to be stateless and solely focused on presentational concerns. Typically, developers pull out those cross-cutting concerns into a higher order component (HOC).

I find that writing HOCs can be very verbose and involves a lot of ceremony surrounding passing props and hoisting statics. It also can lead to prop name conflicts, especially if we’re composing a component using multiple HOCs that might use the same prop names. With multiple HOCs composing a component, it also becomes harder to determine which props come from which HOCs and ensuring you’re handling all of them in your component correctly.

This composition with HOCs also happens only once at compile time. Using render props for composition happens dynamically during render at runtime letting us take advantage of the full React lifecycle.

Here’s an example of an HOC to watch the window scroll position and pass that down to it’s wrapped component:

// Watch scrolling and report 'x' and 'y' position
// to WrappedComponent
const getDisplayName = c => c.displayName || c.name || 'Component';
const withScrollWatch = WrappedComponent => {
return class extends Component {
displayName = withScrollWatch(${getDisplayName(WrappedComponent});

state = { x: 0, y: 0 };

componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}
    handleScroll = e => {
this.setState({
x: window.scrollX,
y: window.scrollY
})
};

render() {
return <WrappedComponent {...this.state} {...this.props} />
}
};
}

And we would then use this to compose with another component that can acceptsx and y props. We’ll simply display the current scroll coordinates on the screen.

// Component to display an 'x' and 'y'
const ShowPosition = ({x, y}) => (
<p style={{ position: 'fixed', top: 0, left: 0}}>
x: {x}, y: {y}
</p>
);
// Compose the above with our HOC
const ScrollDisplay = withScrollWatch(ShowPosition);
render(
<ScrollDisplay />
, document.querySelector('#root'))

Here it is in action (mobile viewers see this demo):

withScroll() Higher Order Component

Instead of statically composing new components with our HOC every time we want to reuse this functionality. Let’s redo this by using render props for composition, instead.

We’ll create a component named ScrollWatch that can be reused with any component through composition using a render prop.

// ScrollWatch - compose any component(s) that need
// to make use of the current 'x' and 'y' scroll position.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ScrollWatch extends Component {
state = { x: 0, y: 0 };
  propTypes = { render: PropTypes.func.isRequired };
  componentDidMount() {
window.addEventListener('scroll', this.handleScroll);
}

handleScroll = e => {
this.setState({
x: window.scrollX,
y: window.scrollY
})
};

render() {
const { x, y } = this.state;
return this.props.render(x,y);
}
}

And, we would make use of it with the same ShowPosition component before like so.

// Component to display and 'x' and 'y'
const ShowPosition = ({x, y}) => (
<p style={{ position: 'fixed', top: 0, left: 0, padding: '5px' }}>
x: {x}, y: {y}
</p>
);
render(
<div>
<ScrollWatch render={
(x,y) => (
<ShowPosition x={x} y={y} />
)
}
/>
</div>
, document.querySelector('#root'));

Here it is in action (mobile readers see this demo).

We didn’t have to go through the ceremony of creating an HOC. We don’t have to worry about prop name conflicts with other composed components, since our render prop function can name the args whatever it wants. This gives us better separation of our presentation components from the prop API imposed by HOCs and more dynamic reuse.

This pattern of composition also makes it easier to design components that have complex, conditional or nested children like some UI components.

Using a navigation bar as an example, let’s assume our navbar can have a brand display, left aligned menu list, and a right aligned menu list. All of these are optional.

It might be composed similarly to the following pseudo markup:

<NavBar>
<NavLeft>
<NavBrand>Comp Co.</NavBrand>
<NavMenu>
<NavItem><a href="">About</a></NavItem>
<NavItem><a href="">Services</a></NavItem>
<NavItem><a href="">Career</a></NavItem>
</NavMenu>
</NavLeft>
<NavRight>
<NavMenu>
<NavItem><a href="">Github</a></NavItem>
<NavItem><a href="">Twitter</a></NavItem>
</NavMenu>
</NavRight>
</NavBar>

If we were to write this component there are a number of things to consider.

  • We need to ensure we get the “right” component types (only NavBrand , NavMenu ,NavItem and the just one of NavLeft and/or NavRight)
  • Ensure we don’t receive duplicates of those accepted children, or have a strategy for handling them.
  • Be sure they are passed in the correct order, or make use of the React.Children API to manipulate them as needed.

This can become more cumbersome over time as the complexity and types of child components and organization possibilities grow.

A more flexible and simpler method for handling these types of complex components is through the use of, surprise…, render props.

Our redesigned NavBar component will take three render props, one for each display area we defined above.

// Simplified NavBar Component
class NavBar extends Component {
propTypes = {
renderBrand: PropTypes.func,
renderLeftMenu: PropTypes.func,
renderRightMenu: PropTypes.func
};
render() {
const {
renderBrand,
renderLeftMenu,
renderRightMenu
} = this.props;

return (
<div className="navbar">
<div className="navbar-left">
<div className="navbar-brand">{ renderBrand() }</div>
<div className="navbar-menu">
{ renderLeftMenu && renderLeftMenu() }
</div>
</div>
<div className="navbar-right">
<div className="navbar-menu">
{ renderRightMenu && renderRightMenu() }
</div>
</div>
</div>
);
}
}

This immediately reduces the complexity required previously with managing directly nested children. It lets us specify what types of components we’d like to render through the props we’ve defined; and because those are render props, we can then have them render in the order and location that we desire.

Here’s the full example (mobile viewers see this demo)

<NavBar/> example using render props

This pattern has potentially wide applicability. Anywhere you are currently using HOCs is a place to consider using render props and a component instead. As I mentioned in the intro, projects like React Router and React Motion are using this pattern successfully.

As an example of something simple, here’s an example of using this pattern for a <Transition/> component that lets you cycle through a list of style objects over a given interval and apply them to anything you want to render.

<Transition/> component example

These are just some of the places I use the render props pattern. It’s really helped clean up my code and simplify my designs and component APIs. Doubtless, there will be different and better patterns as the web and React move forward — and I’m looking forward to learning those when they come. I haven’t stopped using HOCs completely; but I reach for that pattern less and less.

Hopefully, some of these ideas and the render props pattern will find their way into your toolkit as well!

  • Update — there seem to be a number of questions around the performance of inline function expressions in regards to React’s render and reconciliation cycle. While responding to some of the comments I had considered writing a follow up to discuss inline/bound function expressions, their performance and React’s render/reconciliation cycle; but alas, Ryan Florence has just today addressed this issue eloquently. You can read his discussion of just this topic here. Unless you have actually measured a real performance increase and narrowed it down to inline function expressions, you’re prematurely optimizing. Relax, write as you normally would for readability, colocating and expressiveness; then measure when you see an issue and optimize then.

— -

Because the examples I chose make use of position: fixed for showing the scroll position, the Codepen examples don’t work on mobile because of the limitations of iframes on most mobile browsers. You can view the results directly with the link next to each; but you’ll need to view on desktop to see the full code for each examples.


Tag cloud