How to Use Higher Order Components to Reuse Logic in React – CloudSavvy IT


    React logo on a dark background

    Higher Order Components (HOCs) are a kind of React component which help you to reuse logic across your application. The terminology may sound complex but HOCs are easy to get grips with and can make your codebase simpler to maintain.

    A Higher Order Component always wraps child components with additional functionality. A HOC is defined as a function which accepts a component as a parameter. It then returns a new component, which will typically render the input component wrapped with extra props.

    A Simple Example

    The best way to appreciate when HOCs make sense is to see them in action. Let’s consider a simple checkout system where the state of the user’s basket is stored centrally within the application. Our examples show Redux as the state store but this for illustrative purposes only.

    Assume this object represents our app’s state:

    {
        checkout: {
            items: [
                {
                    label: "Product 1",
                    price: 150.00,
                    quantity: 2
                },
                {
                    label: "Product 2",
                    price: 75.00,
                    quantity: 1
                }
            ]
        }
    }

    We have a simple array which represents the items in the user’s basket. Our checkout components are going to derive additional values from this state, such as the total order value and the taxes to apply.

    Our checkout system will probably need to show the total value within multiple independent components. There might be a sidebar widget showing the basket, a post-checkout review screen and a shipping cost calculator. A naive system that simply passed the checkout items as props would risk duplication of logic – each component would need to work out the total order value for itself.

    Introducing the HOC

    Let’s look at how a HOC can help:

    import React from "react";
    import {connect} from "react-redux";
     
    const withCheckout = ComponentToWrap => {
     
        const ComponentWithCheckout = class extends React.Component {
     
            render() {
                return (
                    <ComponentToWrap
                        checkoutItems={this.props.checkout.items}
                        checkoutTotal={this.total}
                        {...this.props} />
                );
            }
     
            get total() {
                const prices = this.props.checkout.items.map(i => (i.quantity * i.price));
                return prices.reduce((a, b) => (a + b), 0);
            }
     
        }
     
        return connect(({checkout}) => ({checkout}))(ComponentWithCheckout);
     
    }
     
    export default withCheckout;

    The file exports a single function, withCheckout, that takes a React component as its only parameter (ComponentToWrap). Within the function, we create a new anonymous class that is itself a React component.

    This new component’s render method creates an instance of the ComponentToWrap we passed into the function. We now have the opportunity to define the instance’s props. We forward the checkout items array as checkoutItems and make the pre-computed total value available as checkoutTotal.

    Any props passed to the HOC are forwarded to the inner component, ensuring that it receives all the data it requires. The function returns the newly created anonymous class which is ready to be rendered throughout your application.

    We use the connect method of react-redux so the checkout prop within the HOC receives the value of the checkout key in our Redux store’s state. This is an implementation detail – your HOC might maintain its own state or reach out to some other service within your application.

    Using the HOC

    Now it’s time to put our HOC to use.

    import React from "react";
    import withCheckout from "./withCheckout.js";
     
    class CheckoutReviewScreen extends React.Component {
     
        render() {
            return (
                <h1>Checkout</h1>
                <h2>{this.props.checkoutTotal}</h2>
            );
        }
     
    }
     
    export default withCheckout(CheckoutReviewScreen);

    We assume our withCheckout HOC is saved to withCheckout.js in the same directory as our new checkout review screen component. By wrapping the component with our withCheckout HOC, we can access and display the total order value. We don’t need to compute it ourselves or store it within the app’s state. If we ever wanted to update how the total is calculated (such as to add a fixed handling charge), we only need to make the change in one place – within our HOC.

    You can now render <CheckoutReviewScreen /> anywhere in your app. Our wrapped example doesn’t need to be passed any props as it sources its data from our Redux store. Because it’s wrapped with withCheckout, itself wrapped with Redux’s connect, the review screen automatically receives a checkoutTotal prop which sums the prices of all the items in the app’s state.

    It’s now worth mentioning how we named our HOC: withCheckout. By convention, HOC names normally have a with prefix because they add something to the components they wrap. In our case, the HOC provides convenient access to our checkout basket which we’d otherwise need to implement within each component.

    Advantages of HOCs

    Using a HOC lets you abstract common behaviours out of components, minimising code duplication and increasing maintainability. HOCs enable a form of dependency injection. They help you keep your components simpler by allowing more to be passed in from the outside world.

    HOCs are common within the React ecosystem. In fact, we’ve seen one within this article – connect(), part of react-redux, which subscribes your components to Redux state changes.

    HOCs are popular because they provide a method of code reuse that doesn’t break the self-containment of components. The pattern utilises React’s composability to let you attach extra functionality without the risk of side-effects.

    You could pass any component in your app to withCheckout without breaking anything – the HOC just attaches a few extra props. This is why it’s so important your HOCs forward all the props they receive ({...this.props} in our example). They mustn’t do anything that could interfere with the wrapped component’s normal operation.

    It may feel like your components now have a dependency on your HOC. This isn’t the case. You could export a second version of your component that isn’t wrapped, giving consumers the choice of which they use.

    Your component actually only insists on receiving certain props – checkoutTotal in our case. This could be supplied by the HOC or by passing a value wherever the component is rendered. Our HOC makes development simpler but hasn’t fundamentally changed the nature of our rendered components.



    Source link