ReactJS – Real World Examples of Higher-Order Components

A Little Bit of Background

Here at REA Group, we've recently been working on a new UI for the core property search experience on realestate.com.au. We've been building this UI as a universal javascript application, supporting both server-side and client-side rendering, using NodeJS and ReactJS.

During this project, we came across a few different use cases for some cross-cutting concerns, such as page load tracking, toggling new features on and off, and desktop/mobile toggling. We wanted to implement these in a generic and reusable way to avoid code duplication. For example, we had different pages (routes) in our application, and wanted to track user visits to those pages, but didn't want to duplicate this tracking code for every route.

We initially used React mixins for some of these problem, but ended up replacing it with higher-order components. In this blog post, I'll first provide a brief introduction to higher-order components (HOCs), and will then go through our journey for each use case and will explain each of the aforementioned techniques (mixins and higher-order component) in more details.



What are Higher-Order Components

Introduction to HOCs

HOCs I think were first introduced in a Github gist by Sebastian Markbåge. If you're familiar with functional programming, you've probably heard of the term "higher-order functions". A higher-order function is a function which either returns another function, or accepts another function as an argument. I'm actually not sure if HOCs were named after higher-order functions; but HOCs, in a similar way to higher-order functions, are implemented as functions which accept a component as an argument and return another component which wraps the given component. Here is a simple example of a HOC which logs the name of wrapped component to console before rendering that component:

const logged = ComposedComponent => React.createClass({
  render() {
    console.log(`Rendering ${ComposedComponent.displayName}`);
    return <ComposedComponent {...this.props} />;
  }
});

This isn't a really useful HOC, but hopefully demonstrates how a HOC can be defined. Assuming you have a component called WeatherWidget, you can use this HOC by passing the component to the logged function when importing the component:

const WeatherWidget = logged(require('./WeatherWidget'));

Now whenever WeatherWidget is rendered, it will be logged to console.

Alternatively, you can apply HOCs when you export a component for example in an index.js file:

module.exports = {
     WeatherWidget = logged(require('./WeatherWidget')
};

Whether you apply an HOC at the time of exporting or importing a component depends on if you want the extended functionality of the HOC to be applied to all the consumers of that component or you want the consumers to choose if they want it applied or not; which is a case-by-case decision.

In addition to render method, HOCs can also implement any of React component lifecycle methods (e.g. componentDidMount) and extend the functionality of wrapped components for those methods.

Why are HOCs Useful

I think two attributes of HOCs make them useful.

Firstly, given an HOC wraps another component, it can:

  • Do things before and/or after it calls that component
  • Avoid rendering the component if certain criteria is not met
  • Update the props passed to that component, or add new props
  • Transform the output of rendering a component (e.g. wrap with extra DOM elements, etc.)

Secondly, because HOCs can be applied to any component, functionality can be implemented once and re-used for every component that needs it.

These two attributes make HOCs a good tool for implementing cross-cutting concerns or common functionalities, such as logging and tracking.

The Relationship Between HOCs and Decorator Design Pattern

If you are familiar with the decorator design pattern in Object-Oriented programming, you may have noticed that HOCs are very similar to decorators. 960px-Decorator_UML_class_diagram.svg.png

Essentially, a decorator:

  • Wraps an existing object
  • Implements the same interface as the wrapped object
  • Extends the functionality of wrapped object by performing extra functionality before and/or after delegating to the wrapped object

In past Java projects, I have used the command pattern to implement different operations and wrapped those command classes with decorator classes in order to extend them with cross-cutting functionality. Such as:

  • Managing database transactions
  • Error (exception) handling
  • Logging
  • Generating and keeping track of a unique transaction ID per command instance, for the purpose of tracking

This approach allows the decorator class functionality to be reused for every type (class) of Command which implemented the same interface.

Despite "interfaces" not existing in Javascript, React components all extend the Component class and must provide a render method::

ReactElement render() {
   //can use this.props and this.state
}

So in the simplest sense, the interface of any React component is a function that maps from props and state (inputs) to a ReactElement (output). The props and state are obviously not explicitly passed to the render method, but are accessible as member variables of component instances. This implicit interface is much more obvious if you implement a component as a stateless function (a feature of React 0.14):

function HelloMessage(props) {
  return <div>Hello {props.name}</div>;
}

ReactDOM.render(<HelloMessage name="Sebastian" />, mountNode);

Given HOCs return a new component which implements render method (and possibly some lifecycle methods) and can be used to extend the wrapped component's functionality, they are very similar to decorators.

Examples of Higher-Order Components

I'm providing three real world use cases of HOCs in our application. I’ll start with a simple example first to explain the concept, before I go through our journey from React mixins to HOCs in the second example. The third example demonstrates how we could come up with new use cases for HOCs once we had gone through that journey.

First Example - Toggling Features On and Off

Our ReactJS UI application has already been released, but we are actively adding new features. We use pull requests when making changes to our codebase. Once a pull request has been reviewed, it is merged and triggers a build, which runs the tests and, if successful, deploys straight to production. Every new feature we develop (let's say adding a nearby schools component to the property details page), is usually broken down to smaller chunks of work for which the team might issue and merge one or multiple pull requests. Until we are ready to go-live with them, new features on the page are hidden from the public. The details of how we configure feature toggles are outside the scope of this post; but for example, by default nearby schools is hidden on the page while it's under development, and can be made visible to specific users for testing.

This meant that we needed a simple way of hiding/showing components on the page based on if a toggle is on or off. We used a very simple HOC for this purpose.

Let's assume, there is a module called featureToggles which can check if a specific toggle is on or off for the current user/request:

module.exports.isOn = function(toggleName) {
   // implementation excluded
   // returns true or false
}

Then, a toggledOn HOC an be implemented as:

const toggledOn = (toggleName, ComposedComponent) => React.createClass({
  render() {
    return featureToggles.isOn(toggleName) ? <ComposedComponent {...this.props} /> : null;
  }
});

As you can see, this HOC simply accepts a toggle name and a component, and only renders that component if the toggle is turned on. So it can be simply used to hide a component on the page by default and to only show it if the toggle is on for the current user/request:

const NearbySchools = toggledOn('nearbySchools', require('./NearbySchools'));

Now, once we're ready to release this feature to everyone, it's just the matter of changing this line to:

const NearbySchools = require('./NearbySchools');

And deploying the application. In this way, new features are released with minimal code change.

Second Example - Tracking Page Loads via Omniture (From Mixins to HOCs)

We needed to track each page visit via Omniture (a web analytics tool, similar to Google Analytics), but we have a few different pages (routes) in our application:

  • Filters page - where users can enter some search criteria (e.g. suburb name or number of bedrooms) for finding properties
  • Search result pages - where properties matching users' search criteria are displayed
  • Property details pages - where details of a specific property are displayed once the user clicks through an item in search results page

The information we needed to track was different for each page. For example, on search result pages we track the current page number, total number of results and the location the user has searched for (e.g. "Richmond, Vic 3121") to for example get an idea of what the most popular suburb search is. Below, I'll go through different solutions we tried and how we ended up with HOCs.

Our Initial Crude Solution

We effectively needed to call our tracking module from componentDidMount and componentDidUpdate methods of our page handler components (handlers are React components responsible for rendering of specific routes), and pass some tracking information specific to each page:

const tracker = require('./tracker');

class SearchResults extends React.Component {
  componentDidMount() {
    tracker.trackPageLoad(this.trackingData());
  }

  componentDidUpdate() {
    tracker.trackPageLoad(this.trackingData());
  }

  trackingData() {
    return {
      pageNumber: this.props.results.pageNumber,
      totalCount: this.props.results.totalCount,
      locations: this.props.filters.locations
    };
  }

  render() {
    // renders search results page
  }
}

Given the way React re-renders components, calling the tracker in componentDidUpdate was necessary for when users navigate from one page of results to the next or previous page; since the SearchResults component won't be re-created, it would only be updated and re-rendered with new props.

The search filters page and property details pages would end up with a very similar implementation for tracking.

There were two main issues with this particular implementation:

  • Code duplication - all different page handler components had to implement the same code in componentDidMount and componentDidUpdate methods. The trackingData implementations were specific to every page, but the tracking calls were not
  • Violation of singe responsibility principle - The page handler components were responsible for both rendering page layouts and tracking page loads

Mixins to the Rescue (Well, Almost!!!)

The first solution which came to mind for addressing these issues, was use of React mixins. You can read about mixins here. They are effectively a way of implementing cross-cutting concerns, by applying a module which implements a specific concern (the mixin) to a React component. For example, the mixin we implemented for page load tracking looked like this:

const pageLoadTrackingMixin = {
  componentDidMount: function() {
    tracker.trackPageLoad(this.trackingData());
  }

  componentDidUpdate: function() {
    tracker.trackPageLoad(this.trackingData());
  }
};

Note that this mixin is assuming that the component which uses it has a trackingData method defined which returns whatever data which needs to be tracked.

Now SearchResults component can be refactored to use this:

const SearchResults = React.createClass({
  mixins: [pageLoadTrackingMixin],

  trackingData() {
    return {
      pageNumber: this.props.results.pageNumber,
      totalCount: this.props.results.totalCount,
      locations: this.props.filters.locations
    };
  },

  render() {
    // renders search results page
  }
});

Note the "mixins" declaration on line 2.

The same mixin can be applied to all other pages. In this way, the component for each page only needs to implement the appropriate trackingData method and mix-in the pageLoadTrackingMixin.

However, there are some issues with this approach too. A number of these issues have been explained in detail in this blog post by Dan Abramov (creator of ReduxJS):

Mixins Are Dead. Long Live Composition

For me, the main one is:

The contract between a component and its mixins is implicit. The mixins often rely on certain methods being defined on the component, but there is no way to see that from the component’s definition.

In our page load tracking example, as mentioned before, the mixin implicitly relies on a trackingData method being implemented by the component.

HOCs to the Rescue

In his blog post linked above, Dan Abramov suggests higher-order components as an alternative to mixins, and that's in fact what we ended up using in this case:

const pageLoadTracking = ComposedComponent => React.createClass({
  componentDidMount() {
    tracker.trackPageLoad(this.props.trackingData);
  },

  componentDidUpdate() {
    tracker.trackPageLoad(this.props.trackingData);
  },

  render() {
    return <ComposedComponent {...this.props} />;
  },

  propTypes() {
    trackingData: React.PropTypes.object.isRequired
  }
});

Note that this HOC is delegating to our tracker module in componentDidMount and componentDidUpdate similar to what the mixin did, but it's explicitly using this.props.trackingData to pass data to trackPageLoad function. This means that when the composed component wrapped by this HOC is rendered, it needs to be passed a trackingData prop:

const SearchResults = pageLoadTracking(require('./SearchResults'));
  ....
  render() {
     const trackingData = // calculate tracking data for search results
     return <SearchResults trackingData={trackingData} otherProps={otherProps} />;
  }
  ...

Now all tracking logic can be removed from SearchResults component, and it is left with only rendering logic:

const SearchResults = React.createClass({
  render() {
    // render search results page
  }
}

Compared to the mixin example, the trackingData contract between pageLoadTracking and its consumers is much more explicit; and the responsibilities of calculating tracking data, page load tracking and rendering are managed in separate modules. Single responsibility principle✅.

This HOC is still as re-usable as the mixin, and can be similarly applied to other pages.

Third Example - Desktop/Mobile Toggle

The UI application that we are building is supposed to be responsive and serve traffic to both mobile and desktop users. Now, you can make a lot of components responsive by applying CSS media queries; but there are some differences in our mobile and desktop designs which cannot be simply achieved by media queries:

  • Some of the other applications we link to on our pages are not responsive, and have different URLs for mobile and desktop. For example, we have different agency profile pages for desktop and mobile, so we need to have different links on our page depending on if the page is rendered on mobile or desktop.
  • Some components need to be displayed in different positions on the page, depending on if the page is rendered on mobile or desktop. So in our page layout, we effectively need to render the same component twice in different positions, and make each appear only on certain screen sizes.

None of these cases can be achieved in a nice way by a simple use of media queries alone. Having have used HOCs in a few cases before, and seeing how expressive they can be, we thought it would be nice if we could create a bunch of HOCs for these specific use cases which read like this:

// Only displays the component on mobile
const SomeComponentOnMobile = mobileOnly(require('./SomeComponent');

// Only displays the component on desktop
const SomeComponentOnDesktop = desktopOnly(require('./SomeComponent');

// Displays on both desktop and mobile, and passes an isDesktop prop to component
const ConditionalComponent = detectDesktopOrMobile(require('./ComponentWithDifferentBehaviourOnDesktopAndMobile'));

Assuming these HOCs, a component can be created for the agency profile link use case above to display different links on different screen sizes:

module.exports = (props) => {
  const link = props.isDesktop ? props.desktopLink : props.mobileLink;
  return <a href={link}>{props.name}</a>;
};

I've used the stateless component syntax here for simplicity. Note that on the line 2, we are using props.isDesktop to check if this component is being rendered on desktop or not, and to render different links based on that. Wrapping this component with the detectDesktopOrMobile HOC would ensure that isDesktop prop gets passed down to this component:

const AgencyProfileLink = detectDesktopOrMobile(require('./AgencyProfileLink'));
...
  <AgencyProfileLink name={agencyName} desktopLink={agencyDesktopLink} mobileLink={agencyMobileLink} />;
...

The same can obviously be achieved by displaying the <a /> tag twice in AgencyProfileLink component, each time with a different CSS class, one which only displays on mobile, and one only on desktop. But I like the expressiveness and testability of the HOC approach. The AgencyProfileLink component can be easily "unit test"ed by providing different isDesktop prop values in the tests, without the need to have a DOM.

For the use case of displaying the same components in different positions on the page on desktop and mobile, a component can be wrapped once in mobileOnly and once in desktopOnly and the two resulting components can then be displayed in different positions on the page:

const SomeComponentOnMobile = mobileOnly(require('./SomeComponent'));
const SomeComponentOnDesktop = desktopOnly(require('./SomeComponent'));
...
  <SomeComponentOnMobile someProps />
  ...
  // further down the page
  <SomeComponentOnDesktop exactSameProps />
...

Again, I really like how HOCs are making the page layout very expressive. It is easy to determine where this component is being displayed on mobile and desktop.

And finally, the implementation of these HOCs is actually pretty simple. Let's start with detectDesktopOrMobile:

module.exports = ComposedComponent => class DetectDesktopOrMobile extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isDesktop: false };
  }

  componentDidMount() {
    const isDesktop = // Use `matchMedia` or check the existence of a DOM element only made visible on desktop via media queries
    this.setState({ isDesktop: isDesktop });
  }

  render() {
    return <ComposedComponent {...this.props} isDesktop={this.state.isDesktop} />;
  }
};

On line 4, the isDesktop state is being initialised to false (This ensures that our pages are rendered for mobile first). Then in componentDidMount on lines 8 and 9, we check if the page has been rendered on a large or small screen, and set the isDesktop flag accordingly. And finally, in render method on line 13, the value of isDesktop state is being passed down to the wrapped component as a prop.

Interestingly, the detectDesktopOrMobile HOC be used for creating desktopOnly HOC:

const detectDesktopOrMobile = require('./detectDesktopOrMobile');

const desktopOnly = ComposedComponent => class DesktopOnly extends React.Component {
  render() {
   return (this.props.isDesktop && <ComposedComponent {...this..props} />);
  }
};

module.exports = ComposedComponent => detectDesktopOrMobile(desktopOnly(ComposedComponent));

Here on line 9, we first wrap the given component in detectDesktopOrMobile HOC; and then call the desktopOnly function. This ensures that isDesktop is passed to the component returned by desktopOnly. Finally on line 5, we only display the wrapped component if isDesktop is set to true.

mobileOnly implementation is obviously very similar. The only difference is that this.props.isDesktop check on line 5 needs to be negated.

HOCs are Composable

In my Java project referred to above, I had composed multiple decorators for each type of command to apply different cross-cutting concerns to them. For example:

private final Command fullyFunctioanlCommandA = new ErrorHandling(new Transactional(new CommandA(arguments)));
private final Command fullyFunctioanlCommandB = new ErrorHandling(new Transactional(new CommandB(arguments)));

Similar to decorators, HOCs can be chained/composed to apply different extended functionalities to a component. For example:

const SuperPowerfulComponent = hoc1(hoc2(require('./SimplePureComponent')));

Summary

Higher-order components are functions which accept a React component as an argument and return another component. The returned component wraps the given component to extend the functionality of it's render method, or any of the other lifecycle methods, by performing extra code before or after calling the wrapped methods or by transforming the input or output of those methods. This makes HOCs a suitable tool for implementing cross-cutting concerns and common functionalities such as feature toggling, logging, tracking, etc.. The results is simple UI components only responsible for rendering UIs and free of other concerns.

HOCs, similar to decorators can be chained together to combine the extended functionalities they each provide. The result of wrapping a component with one or multiple HOCs is still a component with the same interface, which means it can be used in the same way without changing the consumers of that component.

  • bressonnemesis

    Great discussion! Does higher order components work with server rendered React?

    • Mehdi Mollaverdi

      Yes, HOCs would in general work on server-side too, given they are just functions which create another components. We’re in fact using HOCs in a universal/isomorphic application.