How I fell out of love with HOCs

Screen-Shot-2018-12-07-at-3.53.33-PM

Recently, there’s been a lot of buzz about render props over HOCs, but not a lot of real-life examples to showcase this. My team at Gusto recently launched Time Tracking, a feature that lets employers track their team’s hours and sync it directly to payroll, which has been a great use case for using render props.

Screen-Shot-2018-12-07-at-3.24.24-PM

At a high level, both render props and HOCs (Higher Order Components) are a useful way to abstract out reusable code in React and avoid the pains of mixins.

For those who aren’t familiar, here’s a simple example of each:

HOC (Higher Order Component)

An HOC is a function that takes in a component and returns a component. Typically the component returned is wrapped with some additional behavior.

const hocWithUser = WrappedComponent =>  
  class HocWithUser extends React.Component {
    render() {
      const user = { user: 'Alicia' };
      return <WrappedComponent {...this.props} {user} />
    } 
  }

hocWithUser is the function which takes as its parameter a component called the WrappedComponent. hocWithUser’s render function returns WrappedComponent, but with an additional prop called user, in this case specified to be ‘Alicia’.

The way this would be used is:

const Component = props => <div>{props.user}</div>
const ComponentWithUser = hocWithUser(Component);

// This is what will get rendered for ComponentWithUser:
// <div>Alicia</div>

Here, ComponentWithUser is the wrapped result where hocWithUser will supply the “user” prop into Component to be rendered.

Render Prop

A render prop refers to a component that has a “render” function as a prop and calls this instead of implementing its own render.

class User extends React.Component {
  render() {
    return this.props.render({ user: 'Alicia' });
  }
}

Here, User basically defers implementing its own render function, and instead calls its render prop while passing in the user data.

Here’s how this render prop component could be used:

const ShareUser = () =>
  <User render={ ({ user }) => <div>{user}</div> } />
);

// This is what will get rendered for ShareUser:
// <div>Alicia</div>

In ShareUser, we specify what the render function to be passed into User would be. In this case, it’s a function that expects a user object, and then it renders this user and surrounds it with divs.

Render Props vs HOCs

Both render props and HOCs function similarly in that they’re useful in constructing reusable components and sharing that functionality. If we look at how each of these components is used, however, you’ll see that render props are much more explicit about what’s happening. It’s clear that ShareUser will render the user and wrap that in a div. In the case of the HOC, it’s not obvious what’s happening until we look at the internals of hocWithUser.

Great—I already know what render props are, but when would I actually use them?

In my case, I was working on adding a dropdown of pay periods, basically a range of dates, to be added to multiple views. There were several other constraints to consider:

  1. The pay period data needed to be loaded before our other data was fetched so that we knew which dates to fetch data for in the children containers.
  2. The dates are also part of the url so that we have a way of knowing which pay period is selected on page load. Naturally, the dropdown should derive what the selected pay period is, based on the url.
  3. If the dates were invalid or there isn’t one selected, it should just default to the currently active pay period.
  4. This logic is need for multiple views.

Because of the last point, I initially ended up with a lot of code duplication across the multiple views. To consolidate all of that logic, I ended up using both HOCs and render props for a PayPeriodPicker component. The HOC would be in charge of fetching the pay period data since at Gusto we follow a pattern of using HOCs for container components. The render prop would manage the internal state that determines which pay period is selected or defaults to the current one.

This is what the render prop component ended up looking like:

class PayPeriodPicker extends React.Component {
  static propTypes = {
    payPeriods: ImmutablePropTypes.iterable.isRequired,
    render: PropTypes.func.isRequired,
  };

  constructor(props) {
    // logic to determine default/selected pay period
    this.state = { selectedPayPeriod };
  }

  onSelectPayPeriod = payPeriod => {
    // update the url based on selected pay period
  };
 
  render() {
    // logic to map this.props.payPeriods to options 
   const { selectedPayPeriod } = this.state; 
    return this.props.render({
      payPeriodPicker: (
        <Select
          options={options}
          value={selectedPayPeriod}
          requestChange={this.onSelectPayPeriod}
        />
      ),
      selectedPayPeriod,
    })
  }
}

This looks like a typical component up until the render function where it’s not actually rendering an element, but instead calling the render function that is passed into it as props. That render prop function is called with a single parameter that contains the payPeriodPicker select dropdown as well as the dates that should be selected.

Let’s take a look at how this component is used to get a better idea of how it works:

const PayPeriodOverviewPage = props => (
  <PayPeriodPickerContainer
    payPeriodStartDate={payPeriodStartDate}
    payPeriodEndDate={payPeriodEndDate}
    render={({
      payPeriodPicker,
      selectedPayPeriod,
    }) => (
      <PayPeriodOverviewContainer
        payPeriodStartDate={selectedPayPeriod.get('start_date')}
        payPeriodEndDate={selectedPayPeriod.get('end_date')}
        payPeriodPicker={payPeriodPicker}
      />
    )}
  />
);

I didn’t include the implementation of PayPeriodPickerContainer but it’s essentially the container component that fetches the pay periods and maps them to be the props for the PayPeriodPicker component.

Here we pass in payPeriodStartDate and payPeriodEndDate, which are taken from the url and passed into PayPeriodPickerContainer so that it can determine what the selected/default dates are. Then we pass in a render function which takes the payPeriodPicker and selectedPayPeriod object and returns the PayPeriodOverviewContainer, one of the views that requires the dropdown component and the selected pay periods before it can fetch its own data. We just pass in the selectedPayPeriod start and end dates, as well as the payPeriodPicker component.

The render function is a way to have a more explicit API for what data is being passed between the PayPeriodPicker component and the PayPeriodOverviewContainer. This could’ve also been implemented as an HOC, but the details would’ve been abstracted away and you’d end up with something closer to:

const PayPeriodOverviewWithPicker = withPayPeriodPicker(PayPeriodOverviewContainer);

While this is cleaner, it’s harder for someone else to come in and understand what withPayPeriodPicker is actually doing until they look at the implementation of the HOC. With render props, it’s a lot more declarative in what’s being added.

How do we test this?

This assumes setup with enzyme and mocha-chai and sinon.

We can test the render prop like any other function prop: stub it and make sure it’s called with the correct parameters.

const render = () =>
  mount(<PayPeriodPicker {...props} render={renderStub} />);


beforeEach(() => {
  renderStub = sinon.stub();
  render();
});

it('calls render with the correct pay period', () => {
  expect(renderStub).to.have.been.calledWithMatch({  selectedPayPeriod  });
});

Since we’re returning a component to be rendered, let’s specify the render prop function to just render the component and make sure it shows up.

beforeEach(() => {
  renderStub = ({ payPeriodPicker }) => payPeriodPicker;
});

it('renders a Select field', () => {
  expect(render().find(Select)).to.be.present();
});

To test the components that use the render prop, you can stub out what the render function returns and make sure they are used accordingly.

const stubbedPayPeriodPicker = <div>pay period picker stub</div>;
const render = () => 
  mount(<PayPeriodOverviewPage {...props} />);

beforeEach(() => {
  const overviewPage = render();
  payPeriodPickerWrapper = mount(
    overview.find(PayPeriodPickerContainer).prop('render')({
      payPeriodPicker: stubbedPayPeriodPicker,
      selectedPayPeriod,
    });
  );
});

it('renders the overview container with the correct props', () => {
  const overviewContainer = payPeriodPickerWrapper
    .find(PayPeriodOverviewContainer);
  expect(overviewContainer).to.have.props({
    payPeriodPicker: stubbedPayPeriodPicker,
    payPeriodStartDate: selectedPayPeriod.get(‘start_date’),
    payPeriodEndDate: selectedPayPeriod.get(‘end_date’),
  });
});

When should I use render props over HOCs?

Render props were useful for my specific problem due to the hierarchical aspect where pay periods needed to be loaded and configured before we could render the other components, and because this logic was used in multiple places. While I did still use HOCs for data fetching, it could just as easily be rewritten to use render props instead.

The declarative nature of render props makes it way more readable and understandable for other engineers. The other benefit of render props is testing. It’s a lot easier to test each individual component in isolation for both the render prop and the component that encapsulates it. For HOCs, testing the component that encapsulates everything requires knowing the details of how each HOC is implemented and doesn’t lend itself well to stubbing out that functionality.

About the Author

Alicia is a software engineer from Los Angeles who loves coffee too much. She is trying and failing to cut back.