DEV Community

Cover image for React Design Pattern /HOC Pattern
Ogasawara Kakeru
Ogasawara Kakeru

Posted on

React Design Pattern /HOC Pattern

HOC Pattern
Often, we want to use the same logic in multiple components. This logic may involve applying specific styling, requiring authorisation or adding a global state.

A Higher Order Component (HOC) is a component that receives another component as a parameter. It contains the logic that we want to apply to the component passed as a parameter. After applying this logic, the HOC returns the element incorporating the additional logic.

Imagine that we wanted to apply the same styling to multiple components in our application. Instead of creating a 'style' object locally each time, we can simply create a higher-order component (HOC) that adds the 'style' objects to the component we pass to it.

function withStyles(Component) {
  return props => {
    const style = { padding: '0.6rem', margin: '4rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)
Enter fullscreen mode Exit fullscreen mode

We created the StyledButton and StyledText components, which are modified versions of the Button and Text components. Both now contain the style added in the 'withStyles' higher-order component (HOC).

Let's take a look at the CatImages example, which simply renders a list of cat images fetched from an API.

Let's improve the user experience slightly. When fetching data, we should show the user a 'Loading...' screen. Rather than adding to the CatImages component directly, we can use a higher order component that adds this logic for us.

Let's create an HOC called 'withLoader'. An HOC should receive a component and return that component. In this case, the withLoader HOC should receive the element that displays 'Loading...' until the data is fetched.

Now let's create the bare minimum version of the 'withLoader' HOC that we want to use!

function withLoader(Element) {
  return (props) => <Element />;
}
Enter fullscreen mode Exit fullscreen mode

However, we don't just want to return the received element. Instead, we want this element to contain logic that indicates whether or not the data is still loading.

In order to make the 'withLoader' HOC highly reusable, we will not hardcode the Cat API URL in that component. Instead, the URL can be passed as an argument to the HOC, meaning this loader can be used on any component that requires a loading indicator when fetching data from a different API endpoint.

function withLoader(Element, url) {
  return (props) => {};
}
Enter fullscreen mode Exit fullscreen mode

In this case, a HOC returns a functional component with props as the argument, to which we want to add logic to display 'Loading...' while the data is being fetched. Once the data has been fetched, the component should pass the fetched data as a prop.

We have just created a HOC that can receive any component and URL.

  1. In the useEffect hook, the withLoader HOC fetches the data from the API endpoint passed as the value of the URL. While the data hasn't returned yet, we return the element containing the 'Loading...' text.

  2. Once the data has been fetched, we set the value of 'data' to the fetched data. Since 'data' is no longer 'null', we can display the element that we passed to the HOC.

In CatImages.js, we no longer want to export the plain CatImages component. Instead, we want to export the 'wrapped' withLoading HOC around the CatImages component.

export default withLoading(CatImages);
Enter fullscreen mode Exit fullscreen mode

The withLoader HOC also requires a URL to determine which endpoint to fetch the data from. In this case, we want to add the Cat API endpoint.

export default withLoader(
  CatImages,
 "https://api.thecatapi.com/v1/images/search?limit=10"
);
Enter fullscreen mode Exit fullscreen mode

Since the withLoader HOC returned the element with an extra data prop, CatImages in this case, we can access the data prop in the CatImages component.

While the data is being fetched, we can see a 'Loading...' screen.

The Higher Order Component (HOC) pattern enables us to apply the same logic to multiple components while maintaining it all in one place. The withLoader HOC is not concerned with the component or URL it receives; as long as they are valid, it simply passes the data from the API endpoint to the component.

Composing
We can also create multiple higher-order components. For example, we might want to add functionality that displays a 'hovering' text box when the user hovers over the 'Cat Images' list.

We would need to create an HOC that provides a 'hovering' prop to the element that we pass. Based on this prop, we can then render the box conditionally, depending on whether the user is hovering over the 'Cat Images' list.

We can now wrap the withHover HOC around the withLoader HOC.

The 'CatImages' element now contains all the props passed from both 'withHover' and 'withLoader'. We can now render the 'Hovering' text box conditionally, based on whether the value of the 'hovering' prop is true or false.

Hooks
In some cases, we can replace the HOC pattern with React Hooks.

Instead of using the withHover HOC, let's use the useHover hook. Rather than using a higher-order component, we will export a hook that adds mouseOver and mouseLeave event listeners to the element. We cannot pass the element to the hook as we did with the HOC. Instead, we'll return a ref from the hook that will capture the mouseOver and mouseLeave events.

The useEffect hook adds an event listener to the component and sets the hovering value to true or false depending on whether the user is currently hovering over the element. Both the ref and hovering values need to be returned from the hook: ref to add a reference to the component that will receive the mouseOver and mouseleave events, and hovering to enable the Hovering text box to be rendered conditionally.

Rather than wrapping the CatImages component with the withHover HOC, the useHover hook can be used directly inside the CatImages component.

Rather than wrapping the 'DoaImags' component in the 'withHover' component, we can simply use the 'useHover' hook within the component itself.

Generally speaking, React hooks do not replace the HOC pattern.

React DOCs says. 'In most cases, hooks will suffice and can help reduce nesting in your tree.'

As the React documentation tells us, using hooks can reduce the depth of the component tree. Using the HOC pattern can easily result in a deeply nested component tree.

<withAuth>
  <withLayout>
    <withLogging>
      <Component />
    </withLogging>
  </withLayout>
</withAuth>
Enter fullscreen mode Exit fullscreen mode

Adding a hook directly to the component means that we no longer have to wrap components.

Using Higher Order Components makes it possible to provide the same logic to many components while keeping that logic in one place. However, hooks allow us to add custom behaviour from within the component, which could potentially increase the risk of introducing bugs compared to the HOC pattern if multiple components rely on this behaviour.

Best use-cases for a HOC:
・The same uncustomised behaviour must be used by many components throughout the application.

・The component can operate independently, without the additional custom logic.

Best use-cases for Hooks:
・This behaviour must be customised for each component that uses it.

・This behaviour is not spread throughout the application; only one or a few components use it.

・This behaviour adds a number of properties to the component.

Top comments (0)