More

    React Higher Order Component: Everything Defined

    What is React Higher-order component? 

    A React higher order component also known as HOC is an advanced technique of react that is used for reusing component logic. Higher-order components are not a part of the react API. It is a pattern that emerges from React’s compositional nature. 

    Basically, a React Higher Order Component is a function that takes a component and returns a new component. 

    HOCs are common in third-party React libraries, such as Redux’s connect and Relay’s createFragmentContainer. 

     Use React Higher Order Component For Cross Cutting Concerns

    Components are the primary unit of code reuse in React. However, you will find that a few patterns are not a straightforward fit for traditional components. 

     One example is in the case that you have a CommentList component that helps you subscribe to an external data source to render a list of comments: 

    The code will be as follows: 

     

    class CommentList extends React.Component {

      constructor(props) {

        super(props);

        this.handleChange = this.handleChange.bind(this);

        this.state = {

          // “DataSource” is some global data source

          comments: DataSource.getComments()

        };

      }

     

      componentDidMount() {

        // Subscribe to changes

        DataSource.addChangeListener(this.handleChange);

      }

     

      componentWillUnmount() {

        // Clean up listener

        DataSource.removeChangeListener(this.handleChange);

      }

     

      handleChange() {

        // Update component state whenever the data source changes

        this.setState({

          comments: DataSource.getComments()

        });

      }

     

      render() {

        return (

          <div>

            {this.state.comments.map((comment) => (

              <Comment comment={comment} key={comment.id} />

            ))}

          </div>

        );

      }

    }

     

    The next step is to write a component for subscribing to a single blog post. This follows largely similar pattern. 

     

    class BlogPost extends React.Component {

      constructor(props) {

        super(props);

        this.handleChange = this.handleChange.bind(this);

        this.state = {

          blogPost: DataSource.getBlogPost(props.id)

        };

      }

     

      componentDidMount() {

        DataSource.addChangeListener(this.handleChange);

      }

     

      componentWillUnmount() {

        DataSource.removeChangeListener(this.handleChange);

      }

     

      handleChange() {

        this.setState({

          blogPost: DataSource.getBlogPost(this.props.id)

        });

      }

     

      render() {

        return <TextBlock text={this.state.blogPost} />;

      }

    }

     

    CommentList and BlogPost are not identical in any way. They help access different methods of DataSource and render different outputs. The similarity is their implementation- 

     

          On mount, add a change listener to DataSorce

          Inside the listener, call setState whenever the data source changes

          On unmount, remove the change listener. 

     

    In a large app, the same process of subscribing to DataSource and calling setState is repeated multiple times. We want an abstraction that allows us to define this logic in a single place and share it across many components. This is where Higher-order Components strive. 

     

    We can write functions to create components, like CommentList and BlogPost, that subscribe to DataSource. 

    Let us name the function withSubscription: 

     

    const CommentListWithSubscription = withSubscription(

      CommentList,

      (DataSource) => DataSource.getComments()

    );

     

    const BlogPostWithSubscription = withSubscription(

      BlogPost,

      (DataSource, props) => DataSource.getBlogPost(props.id)

    );

     

     The first parameter is the wrapped component. The second parameter retrieves the data we’re interested in, given a DataSource and the current props. 

     When CommentListWithSubscription and BlogPostWithSubscription are rendered, CommentListand BlogPost will be passed a data prop with the most current data retrieved from DataSource:

     

     

    c // This function takes a component…

    function withSubscription(WrappedComponent, selectData) {

      // …and returns another component…

      return class extends React.Component {

        constructor(props) {

          super(props);

          this.handleChange = this.handleChange.bind(this);

          this.state = {

            data: selectData(DataSource, props)

          };

        }

     

        componentDidMount() {

          // … that takes care of the subscription…

          DataSource.addChangeListener(this.handleChange);

        }

     

        componentWillUnmount() {

          DataSource.removeChangeListener(this.handleChange);

        }

     

        handleChange() {

          this.setState({

            data: selectData(DataSource, this.props)

          });

        }

     

        render() {

          // … and renders the wrapped component with the fresh data!

          // Notice that we pass through any additional props

          return <WrappedComponent data={this.state.data} {…this.props} />;

        }

      };

    }

     

    Note that a HOC doesn’t modify the input component and neither does it use inheritance to copy its behavior. A HOC instead composes the original component by wrapping it in a container component. A HOC is a pure function and has no side effects. 

     

     Don’t Mutate the Original Component. Use Composition. 

     

    Resist the temptation to modify a component’s prototype inside a HOC. 

     

     

    function logProps(InputComponent) {

      InputComponent.prototype.componentDidUpdate = function(prevProps) {

        console.log(‘Current props: ‘, this.props);

        console.log(‘Previous props: ‘, prevProps);

      };

      // The fact that we’re returning the original input is a hint that it has

      // been mutated.

      return InputComponent;

    }

     

    // EnhancedComponent will log whenever props are received

    const EnhancedComponent = logProps(InputComponent);

     

     

    Mutating HOCs are a leaky abstraction and come with their own problems. The consumer must know how they are implemented in order to avoid conflicts with other HOCs. 

     

    HOCs should use compositions instead of mutation, by wrapping the input component in a container component. 

     

     

     

     

    function logProps(WrappedComponent) {

      return class extends React.Component {

        componentDidUpdate(prevProps) {

          console.log(‘Current props: ‘, this.props);

          console.log(‘Previous props: ‘, prevProps);

        }

        render() {

          // Wraps the input component in a container, without mutating it. Good!

          return <WrappedComponent {…this.props} />;

        }

      }

    }

     

     

    This new HOC has the same functions as the mutating version but avoids all causes for potential clashes. Because this is a pure function, it is compatible with other HOCs and even with itself. 

     

    Container Components are a part of a strategy of separating responsibility between high and low-level concerns. HOCs use containers as a part of their implementation. 

     

     Convention: Pass Unrelated props Through to the WRAPPED COMPONENTS

     

    HOCs add features to a component and these do not alter its contract. HOCs need to pass through props that are unrelated to its specific concern. Most HOCs use a method that looks similar to this:

     

    render() {

      // Filter out extra props that are specific to this HOC and shouldn’t be

      // passed through

      const { extraProp, …passThroughProps } = this.props;

     

      // Inject props into the wrapped component. These are usually state values or

      // instance methods.

      const injectedProp = someStateOrInstanceMethod;

     

      // Pass props to wrapped component

      return (

        <WrappedComponent

          injectedProp={injectedProp}

          {…passThroughProps}

        />

      );

    }

    This convention helps ensure that HOCs are as flexible and as reusable as possible. 

     

     Convention: Maximizing Composability

     

    Not all HOCs look the same. Sometimes they accept only a single argument which is the wrapped component. 

    const NavbarWithRouter = withRouter(Navbar);

     

    Generally, HOCs accept additional arguments. In this example, a config object is being used to specify the data dependencies of a component. 

    const CommentWithRelay = Relay.createContainer(Comment, config);

     

    The most common signatures for HOCs look like this: 

    // React Redux’s `connect`

    const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

     

    When we break it apart, it is easier to see what is happening: 

    // connect is a function that returns another function

    const enhance = connect(commentListSelector, commentListActions);

    // The returned function is a HOC, which returns a component that is connected

    // to the Redux store

    const ConnectedComment = enhance(CommentList);

     

    In other words, we need to connect a higher-order function that returns a higher order component. 

    Single argument HOCs have the signature Component => Component. Functions whose output type is the same as the input are really. Easy. To compose together. 

    // Instead of doing this…

    const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

     

    // … you can use a function composition utility

    // compose(f, g, h) is the same as (…args) => f(g(h(…args)))

    const enhance = compose(

      // These are both single-argument HOCs

      withRouter,

      connect(commentSelector)

    )

    const EnhancedComponent = enhance(WrappedComponent)

     

    Wrap the Display Name for Easy Debugging

     

    The container components created by React Higher Order Components show up in the React Developers Tools and to ease debugging, we need to choose a display name that communicates that it is the final product of an HOC. 

     

    function withSubscription(WrappedComponent) {

      class WithSubscription extends React.Component {/* … */}

      WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;

      return WithSubscription;

    }

     

    function getDisplayName(WrappedComponent) {

      return WrappedComponent.displayName || WrappedComponent.name || ‘Component’;

    }

     

    Do not use HOCs Inside the render Method

    If the component returned from render is identical to the component from the previous render, React automatically updates the subtree by substituting it with a new one. If they are not equal, the previous subtree is unmounted completely. 

     

    This matters in HOCs because it means you can’t apply a HOC to a component withing the render method: 

    render() {

      // A new version of EnhancedComponent is created on every render

      // EnhancedComponent1 !== EnhancedComponent2

      const EnhancedComponent = enhance(MyComponent);

      // That causes the entire subtree to unmount/remount each time!

      return <EnhancedComponent />;

    }

     

    Remounting a component causes the state of the component and all its children to be lost. 

    In rare cases where an HOC needs to be applied dynamically, it can also be done inside a component’s lifecycle method or its constructor. 

     

    Static Methods Must Be Copied Over

    Sometimes, it is useful to define a static method on a React component. 

    When an HOC is applied to a component, the original component is wrapped in a container component. This means the new component does not have any of the static methods of the original component. 

     

    // Define a static method

    WrappedComponent.staticMethod = function() {/*…*/}

    // Now apply a HOC

    const EnhancedComponent = enhance(WrappedComponent);

     

    // The enhanced component has no static method

    typeof EnhancedComponent.staticMethod === ‘undefined’ // true

    To Solve this, you could copy methods to containers before returning it. 

    function enhance(WrappedComponent) {

      class Enhance extends React.Component {/*…*/}

      // Must know exactly which method(s) to copy 🙁

      Enhance.staticMethod = WrappedComponent.staticMethod;

      return Enhance;

     

    Another possible solution is to export the static method separately from the component itself. 

    // Instead of…

    MyComponent.someFunction = someFunction;

    export default MyComponent;

     

    // …export the method separately…

    export { someFunction };

     

    // …and in the consuming module, import both

    import MyComponent, { someFunction } from ‘./MyComponent.js’;

     

    Refs Aren’t Passed Through

    While the convention for react higher-order components is to pass through all props to the wrapped component, this does not work for refs. That’s because ref is like a key that is handled by React.  If you add a ref to an element whose component is the result of a HOC, the ref refers to an instance of the outermost container component, not the wrapped component.

    The solution for this problem is to use the React.forwardRef API (introduced with React 16.3). 

     

     

     

    Recent Articles

    Next JS Development Service – Features, Pros, Cons, FAQ

    Fundamentally, we believe it is the single most critical benefit in a rapidly evolving digital environment. Since it allows us to swiftly...

    React JS Learning Worth?

    What is the guideline to React JS Learning? There are several clear talents you must master if you want to make a career change...

    React JS vs React Native – Features, Pros & Cons

    Do you have trouble deciding between React JS vs react native for your development process? Do you need a solid web development framework or...

    Next js vs React: Who Wins The Battle

    Next js vs React: Who Wins The Battle Are you a programmer or in charge of your firm's digital strategy? Do you want to build...

    How can artificial intelligence (AI) help an employee’s work performance and proactivity?

    Artificial intelligence can assist human labor by doing inefficient jobs. Human employees may focus on more challenging work while robots and artificial intelligence power...

    Related Stories

    Leave A Reply

    Please enter your comment!
    Please enter your name here