Higher Order Components (HoCs) in React

Written by vladmihet | Published 2022/04/25
Tech Story Tags: react | reactjs | react-hook | react-hooks | javascript | web-development | higher-order-components | react-native-development

TLDRHOCs are a design pattern derived from the compositional nature of React, which allows us to derive components based on particular data we want to pass down to them. Components are functions that return a React element (JSX) and higher-order components are function that return those components. An HOC would look something like this: withHOC = (Component, someData, otherArgs) or withHoc from 'previous-gist' or 'withHoc' HOC is not a component, it will only render a list of blog posts in the application.via the TL;DR App

Higher-Order Components are actually a design pattern derived from the compositional nature of React, which allows us to derive components based on particular data we want to pass down to them.

Think of it this way:

  • Components return React Elements (JSX)
  • Higher-Order Components return Components

Don’t get confused — a HOC itself is not a component.

Remember: Components are functions that return a React element (JSX Element), higher-order components are the functions that return those components.

At a higher level of abstraction, an HOC would look something like this:

const withHOC = (Component, someData, otherArgs) => {
  return (props) => {
    return (
      <Component
        customProp={someData.firstProp}
        dataSomething={otherArgs.someOtherDataProp}
        {...props}
      >
        <h2>Might as well add nested elements</h2>
      </Component>
    );
  };
};

with … symbolizes the defacto (the most commonly used) naming convention when working with HOCs

And a non-practical example of how this would be used:

import withHoc from 'previous-gist';

const Component = (props) => {
  return (
    <div>
      <h2>Hello!</h2>
    </div>
  );
};

export default withHOC(
  Component,
  { // `someData` argument
    prop1: 'what's good?',
    prop2: { propProperty: 'holding some data' },
  },
  { // `otherArgs` argument
    arg1: { property: 'some-value' }
  },
)();


Now that we’ve gotten a bit more familiar with this concept, let’s go into a more realistic scenario where you might find yourself in need of HOCs.


Project Setup

Folder Structure:

You can check out the repo here: https://github.com/Vlad-Mihet/React-HoCs for the entire project file system structure.

Brief overview:

  • src/api: will contain the stored blog posts, with an afferent getter method for retrieval
  • src/components: will contain generic components; in our case, it will be BlogsContainer: which will act as a presentational component, and only display the blog posts passed down to it
  • src/views: will contain the views of the application; in our case, it will only be the Home view, which will render a list of Recent, Popular & Archived blog posts through the container components in src/views/home/components
  • src/views/home/components: will contain Home view related components; in our case, these will be all container components: ArchivedBlogsPopularBlogs, and RecentBlogs, which will all be responsible for fetching their own data
  • src/App.js: will embed the Home view

So, what is the issue with the setup?

Well, we do have different components for a rather similar task: Initializing a piece of state for blogs, receiving the same blogs prop, updating the blogs state at mount time, and rendering it with the same BlogsContainer component.

It is code duplication that could be solved through the implementation of a Higher Order Component.

Solution

Create an HOC withBlogs under src/hocs with the following content:

import { useEffect, useState } from "react";

const withBlogs = (Component, retrieveBlogs) => (props) => {
  const [blogs, setBlogs] = useState([]);

  useEffect(() => {
    setBlogs(retrieveBlogs);
  }, []);


  return (
    <Component blogs={blogs} {...props} />
  );
};

export default withBlogs;

The withBlogs HOC would get a Component, as well as a blogs retrieval method as parameters, and will be tasked with fetching the blogs data and returning a component with the blogs prop set as the retrieved blogs data.

Next, in the Home view, we could discard the components we have used from src/views/home/components, and define the new components created by our new HoC:

import React from 'react'
import { getArchivedBlogs, getPopularBlogs, getRecentBlogs } from '../api';
import BlogsContainer from '../components/BlogsContainer';
import withBlogs from '../hocs/withBlogs';

const RecentBlogs = withBlogs(BlogsContainer, getRecentBlogs);

const PopularBlogs = withBlogs(BlogsContainer, getPopularBlogs);

const ArchivedBlogs = withBlogs(BlogsContainer, getArchivedBlogs);

const Home = () => {
  return (
    <div className='home'>
      <section>
        <h2>Recent Blogs</h2>
        <RecentBlogs />
      </section>
      <section>
        <h2>Popular Blogs</h2>
        <PopularBlogs />
      </section>
      <section>
        <h2>Archived Blogs</h2>
        <ArchivedBlogs />
      </section>
    </div>
  );
};

export default Home;

Not only have we reduced code duplication, but we have also implemented a simpler and more easily-readable solution.


Now, you might be wondering, why don’t we simply use the BlogsContainer component inside the withBlogs HOC, rather than using a generic parameterized component?

Well, what if we ever want to maintain the same functionality, but in another case? For example, we might have a banner component that fetches blogs, but displays them one by one in a vertical scrolling fashion?

In that case, we won’t be passing props to style the BlogsContainer component, but rather have another Banner component.


Should you still use HOCs?

Well, it depends. Is the project you are working on using Class-based components? If yes, it’s pretty likely you would need to use HOCs, since hooks are not available for them. Otherwise, I do consider that hooks are still a better approach to the issue we have had an overview of.

Let’s see how hooks would solve our issue:

First, we would have to create an useBlogs hooks to handle the retrieval of the blogs data:

import { useState, useEffect } from 'react';

const useBlogs = (getBlogs) => {
  const [blogs, setBlogs] = useState([]);

  useEffect(() => {
    setBlogs(getBlogs());
  }, []);

  return [blogs, setBlogs];
};

export default useBlogs;

And then we’ll have to update the components’ definitions in the Home view:

import React from 'react'
import { getArchivedBlogs, getPopularBlogs, getRecentBlogs } from '../api';
import BlogsContainer from '../components/BlogsContainer';
import useBlogs from '../hooks/useBlogs';

const RecentBlogs = (props) => {
  const [blogs] = useBlogs(getRecentBlogs);

  return <BlogsContainer blogs={blogs} {...props} />
};

const PopularBlogs = (props) => {
  const [blogs] = useBlogs(getPopularBlogs);

  return <BlogsContainer blogs={blogs} {...props} />
};

const ArchivedBlogs = (props) => {
  const [blogs] = useBlogs(getArchivedBlogs);

  return <BlogsContainer blogs={blogs} {...props} />
};

const Home = () => {
  return (
    <div className='home'>
      <section>
        <h2>Recent Blogs</h2>
        <RecentBlogs />
      </section>
      <section>
        <h2>Popular Blogs</h2>
        <PopularBlogs />
      </section>
      <section>
        <h2>Archived Blogs</h2>
        <ArchivedBlogs />
      </section>
    </div>
  );
};

export default Home;

Through the hooks approach, we can benefit from handling the compositional aspect of React much more elegantly, not having to define a boilerplate HOC, but rather a nicer hook, and use that as needed, without having to go through multiple component factories just to get various props.

It also allows us greater control over the state, which is a huge plus in case we need to do some more extensive computation on the blog state of each component, like filtering or sorting through the blogs.


Final Word

I do believe that HOCs are extremely useful to understand, but if you are not actively working with Class components, I strongly suggest you would look into hooks, as they are simpler to work with overall.

I hope you have learned something new by reading this article, and hope you have enjoyed it. Catch you on the next one!

Cheers!

If you have enjoyed reading this article, you can also support me by buying me a coffee here.


Also published here


Written by vladmihet | Full-Stack Engineer
Published by HackerNoon on 2022/04/25