Decorating React Components Using Higher Order Components and Inheritance Inversion

Higher Order Component is a very powerful pattern in React with which we can use functions to turn a React Component into another React Component. In very simplistic terms, a Higher Order Component is a function which takes a React Component and returns another React Component.

HigherOrderComponent = Component => Component;  

There are 2 ways to achieve this, either by returning a new Component which wraps the existing Component, or by returning a new Component which inherits the existing Component. Both has its own advantages, but in this example I will be using the latter, which is also known as Inheritance Inversion. The Higher Order Component will be defined similar to this.

const HigherOrderComponent = (Component) => {  
  return class extends Component {
  }
}

The returned class is intentionally left anonymous, as we do not want to couple a component name to the Higher Order Component. This will be of benefit when we start looking into composing multiple Higher Order Components.

In this example, we will create a simple component to display a header in an application. We will then use Higher Order Components to enhance the header component.

So let’s start with a basic React App to render a Title component to the DOM.

class App extends React.Component {  
  render() {
    return <Title />
  }
}

render(<App />, document.getElementById('app'));  

Title is a very simple Component.

class Title extends React.Component {  
  render() {
    return (
      <h1>HOC is Awesome!</h1>
    )
  }
}

export default Title;  

The result is a boring looking title.

Let’s add a border to distinguish the title a little more. Of course, we’ll do this using a Higher Order Component, aptly named withBorder.

const withBorder = (WrappedComponent) => {  
  return class extends WrappedComponent {
    componentDidMount() {
      if (super.componentDidMount) super.componentDidMount();
      const node = findDOMNode(this);
      node.classList.add('border');
    }
  }
};

export default withBorder;  

Remember that a Higher Order Component takes a source React Component and returns another React Component that inherits from the source. In this case, the returned class has additional behaviour defined in the componentDidMount React lifecycle hook. Since we are inheriting from a React Component, we have to ensure that any existing behaviour defined for the same lifecycle hook in the parent remains. We do this by calling super.componentDidMount(). In this example, the Higher Order Component simply adds a .borderclass to the DOM element rendered.

Now to use the withBorder Higher Order Component, we simply import withBorder in the Title and replace the export value with a call to withBorder.

// export default Title;
export default withBorder(Title);  

Our header is looking better, but it looks like it needs some breathing space. So let’s add some padding to it. We can create another Higher Order Component, named withPadding.

const withPadding = (WrappedComponent) => {  
  return class extends WrappedComponent {
    componentDidMount() {
      if (super.componentDidMount) super.componentDidMount();
      const node = findDOMNode(this);
      node.classList.add('padding');
    }
  }
};

export default withPadding;  

Now we have 2 Higher Order Components: withBorder and withPadding. How do we use both of them? Remember that Higher Order Components are simply a function that takes a component and return another component. So we can pass one to another.

// export default withBorder(Title);
export default withPadding(withBorder(Title));  

Let’s add more pop to the header with some colour and background.

const withBackground = (WrappedComponent) => {  
  return class extends WrappedComponent {
    componentDidMount() {
      if (super.componentDidMount) super.componentDidMount();
      const node = findDOMNode(this);
      node.classList.add('background');
    }
  }
};

export default withBackground;  
const withColour = (WrappedComponent) => {  
  return class extends WrappedComponent {
    componentDidMount() {
      if (super.componentDidMount) super.componentDidMount();
      const node = findDOMNode(this);
      node.classList.add('color');
    }
  }
};

export default withColour;  

Using them the same way we did previously, we can apply the colour and background to the header.

// export default withPadding(withBorder(Title));
export default withColour(withBackground(withPadding(withBorder(Title))));  

However, this is starting to look unwieldy and difficult to read. At this point, we can use the compose function from the recompose package, which allows us to compose Higher Order Components before applying it to a component.

// export default withColour(withBackground(withPadding(withBorder(Title))));
export default compose(withColour, withBackground, withPadding, withBorder)(Title);  

It looks good to me now. Some of you might notice repetition in the decorator Higher Order Components, where the only difference between each of them is the name of the class added to the node. We can further refactor the Higher Order Components to accept a class name parameter, but I will not go into the details here.

const withDecoration = (decorationName) => (Component) => DecoratedComponent  

So far we’ve been using the Higher Order Component to add behaviours to the component’s componentDidMount hook. What if we want to render something different?

Let’s take the case where we need to apply a layout to the page using flexbox. We need to contain the header within a container with the following CSS style, which centers the header in the screen.

display: flex;  
height: 100vh;  
align-items: center;  
justify-content: center;  

To do this, we will wrap the App React component with a Higher Order Component with the flex properties.

const withFlexContainer = (WrappedComponent) => {  
  return class extends WrappedComponent {
    constructor(props) {
      super(props);
      this.style = {
        display: 'flex',
        height: '100vh',
        alignItems: 'center',
        justifyContent: 'center'
      }
    }

    render() {
      return (
        <div style={this.style}>
          {super.render()}
        </div>
      )
    }
  }
};

export default withFlexContainer;  

In this Higher Order Component, we added behaviour in both constructor and render. The new constructor is similar to other constructor where we call super(props) and define an attribute style. What is interesting is in render, where we return a new DOM element with the flex properties, and then we call super.render() to render the original component’s DOM element.

We’ll use this when rendering our App component to the DOM.

// render(<App />, document.getElementById('app'));

const FlexApp = withFlexContainer(App);  
render(<FlexApp />, document.getElementById('app'));  

There it is, a nicely centered header.

So far we’ve done the following using Higher Order Components:

  • Add new behaviour to a component’s lifecycle function
  • Add new behaviour to a component’s render function
  • Combining multiple higher order components through composition
  • Using recompose package to help in composition

This is only the tip of the iceberg when it comes to the application of Higher Order Components. Let me know if you have any other interesting and clever use cases.

Albert Salim

Software developer at ThoughtWorks, part time triathlete, occasional photographer.

Subscribe to Albert Salim

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!