React Higher-Order Components
Overview
Higher-order components, abbreviated as HOC, are a sophisticated approach to reusing component functionality logic. Higher-order components React takes one component as input and returns another component as output.
Introduction
JavaScript functions used to extend the functionality of an existing component are known as higher order components. A typical React HOC is described as follows in the documentation for React :
A higher-order component is a function that takes in a component and returns a new component.
Syntax
Because higher-order functions are pure, they receive input and return output values following that input. If the value is changed, higher-level functions run again with new data input. We don't need to alter the higher-order components to modify our returning component. All we have to do is modify the data that our higher-order function is consuming.
Let's look at an example :
Use HOCs for Cross-Cutting Concerns
In React, components serve as the main unit of code reuse. However, you'll discover that some layouts don't fit standard components easily.
Let's take an example where there is a CommentList component that renders a list of comments by subscribing to an external data source :
You then create a part for subscribing to a single article that follows a similar structure :
BlogPost and CommentList call separate methods on DataSource and produce different results, therefore they are not identical. But a large portion of their execution is the same.
You can imagine that in a large project, this identical practice of subscribing to DataSource and invoking setState will be repeated many times. We need an abstraction that enables us to declare this logic once and share it among other components. When it comes to this, higher-order components emerge.
We can develop a method that generates components such as CommentList and BlogPost that subscribe to DataSource . The function will take as one of its parameters a child component that receives the subscription data as a prop. Let's call the method withSubscription :
The wrapped component is the first parameter. With a DataSource and the current props, the second parameter fetches the data that we are searching for.
The most recent data from DataSource will be provided as a data prop to CommentList and BlogPost when CommentListWithSubscription and BlogPostWithSubscription are rendered :
It should be noted that a HOC neither alters the input component nor copies its behavior using inheritance. Instead, a HOC wraps the original component in a container component to construct it. A higher-order component is a pure function without any side effects.
That's all, then! The wrapped component receives all of the container's props, as well as a new prop, data, which is used to render the output. The wrapped component is not concerned with the origin of the data, and neither is the HOC with how or why the data is used.
WithSubscription is a regular function, thus you are free to include as many or as few parameters as you like. For example, to further separate the HOC from the wrapped component, you could make the name of the data prop customizable. Alternatively, you may accept a parameter that configures shouldComponentUpdate or the data source. All of these are feasible due to the HOC's complete control over the component's definition.
The contract between the wrapped component and withSubscription is props-based, just like components. This makes switching between HOCs simple as long as they give the wrapped component the same props. If you switch data-fetching libraries, for instance, this might be helpful.
Don’t Mutate the Original Component, Use Composition
Avoid the temptation to alter (or otherwise mutate) a component's prototype inside a higher-order component. Several issues arise with this. One of them is the inability to reuse the input component without the enhanced component. More importantly, the functionality of the first higher-order component will be overwritten if you apply another higher-order component to EnhancedComponent that also modifies componentDidUpdate! This higher-order component will also not operate with function components that lack lifecycle methods.
Mutating higher-order components is a leaky abstraction—to prevent conflicts with other higher-order components, the consumer must understand how they are implemented.
Instead of using mutation, HOCs should utilize composition by wrapping the original component in a container component :
This HOC avoids potential conflicts while maintaining the same function as the modifying version. It is compatible with both class and functional components. It can also be combined with other HOCs as well as with itself because it is a pure function.
A Pure Function is a function (a piece of code) that always returns the same result when given the same arguments. It doesn't rely on any data or state changes that occur while a program is running. Instead, it is solely dependent on the ideas it receives.
Higher-order components and container components' design patterns may have some similarities. Container components are part of an approach to divide responsibilities between high-level and low-level issues. Containers control state and subscriptions and send props to components that control UI rendering and other tasks. Containers are used as part of the HOC implementation. HOCs can be compared to the definitions of parameterized container components.
Convention: Pass Unrelated Props through the Wrapped Component
HOCs give a component new features. They shouldn't make significant changes to their contract. The component returned by a HOC is anticipated to have a similar approach to the wrapped component.
HOCs should go through props that do not correspond to their specific concern. Most HOCs include a render method that looks like this :
Convention: Maximizing Composability
Not all HOCs are the same. Sometimes they just take the wrapped component as an argument :
HOCs usually allow additional arguments. In this Relay example, the data dependencies of a component are specified via a config object :
The most typical HOC signature is as follows :
What?! It is simpler to understand what is happening if we break it down.
Connect is a function that returns another function :
Connect is a HOC that returns a HOC!
This form may appear complex or unneeded, yet it serves a purpose. HOCs with only one argument has the signature Component => Component, like the one the connect function returns. It is pretty simple to combine functions whose input type and output type match.
Many third-party libraries, like Redux and Ramda, offer the compose utility function.
Convention: Wrap the Display Name for Easy Debugging
When using the React Developer Tools, the container components produced by HOCs are displayed like other components. Select a display name that makes it clear that it is the outcome of a HOC to make debugging easier.
The most popular method is to wrap the wrapped component's display name. So, if the display name of the wrapped component is CommentList and the name of your higher-order component is withSubscription, just use the display name WithSubscription(CommentList) :
Caveats
There are a few limitations with higher-order components that aren't readily evident if you're new to React.
Don’t Use HOCs Inside the Render Method
React's reconciliation mechanism employs component identity to determine if an existing subtree should be updated or a new one should be mounted between two renders. As a result, component identification needs to be constant across renderings.
Additionally, React must determine whether updating the DOM is necessary when a component's state changes. A virtual DOM is created and compared with the actual DOM. The component's new state will be present in the virtual DOM in this case.
The performance of the entire app is impacted if HOCs are used inside the render method since their identities cannot be maintained over render.
However, the issue here goes beyond performance, when a component is remounted, the state of both the parent component and every one of its child components is lost.
Instead, we should use HOCs outside of the component definition to ensure that the final component is only created once.
Static Methods Must Be Copied Over
Adding a static method to a React component can be helpful sometimes. To make it easier to compose GraphQL fragments, Relay containers, for instance, expose the static method getFragment.
However, when a HOC is applied to a component, the original component is wrapped in a container component. This indicates that none of the static methods from the original component is present in the new component.
Before returning the container, you might copy the functions onto it to fix this :
However, you must be aware of which methods must be reproduced. To automatically replicate all non-React static methods, use hoist-non-react-statics :
The static method can be exported independently of the component itself as an alternative approach.
Refs aren’t Passed Through
While it is common to practice for higher-order components to send all props through to the wrapped component, it does not work for refs. This is because the ref is not truly a prop and React handles it differently from the key. A ref that is added to an element whose components are resulted from a HOC refers to an instance of the uppermost container component rather than the wrapped component.
Use the React.forwardRef API to find a solution to this issue (introduced with React 16.3).
React forwardRef is a function that lets parent components "forward" refs to their children. In React, forwardRef provides a reference to a DOM element to the child component created by the parent component. As a result, the child is now able to read and alter that element wherever it is utilized.
Benefits of Higher-Order Components
- When used correctly and effectively, high-order components are simple to manage.
- High-order components react allowing us to avoid altering or comoving the same functionality in each component created.
- Coding is easier to read and more effective when using high-order components react. Code debugging is made simple by the correct naming conventions.
Conclusion
- A function that accepts one component and outputs another is referred to as a higher-order component.
- Higher-order components react to let programmers reuse code logic throughout their projects. As a result, there will be less redundancy and more streamlined, readable code.
- To effectively debug and read the code, choose a display name.
- Invoking the HOC within the render means that the HOC component is invoked every time. This hurts performance. It is preferable to call HOC from an external component.
- HOC side effects should be avoided. HOC should design a component that can be reused. The component given as an argument should not be modified.