Modern HOCs with Hooks and Context API
Tags: react, hooks, hoc, contextapi
Published: 2021-09-29 17:57:10

---
title: Modern HOCs with Hooks and Context API
published: true
description: Example of using HOCs with hooks and Context API
tags: react, hooks, hoc, contextapi
//cover_image: https://direct_url_to_image.jpg
---

Using the Context API sometimes makes us do some abstractions to avoid repeating code declarations, It's possible to have abstractions only with hooks too, but we can have a similar result with more composition with HOCs.

For example, here we have a common context that we'll integrate with a HOC:

```typescript
import {
  createContext,
  useReducer,
  useMemo,
  useContext,
  ReactNode
} from "react";

type ContextProps = {
  isLoading: boolean;
  showError: boolean;
};

export type GlobalContextProps = {
  state: ContextProps;
  dispatch: (a: Action) => void;
};

const initialState: ContextProps = {
  isLoading: false,
  showError: false
};

export enum ACTIONS {
  IS_LOADING = "IS_LOADING",
  SHOW_ERROR = "SHOW_ERROR"
}

export type Action = {
  type: ACTIONS;
  payload: boolean;
};

export const GlobalContext = createContext<GlobalContextProps>({
  state: initialState,
  dispatch: () => {}
});

const reducer = (state: ContextProps, action: Action) => {
  const { type, payload } = action;
  switch (type) {
    case ACTIONS.IS_LOADING:
      return {
        ...state,
        isLoading: payload
      };
    case ACTIONS.SHOW_ERROR:
      return {
        ...state,
        showError: payload
      };
    default:
      return state;
  }
};

interface IGlobalProvider {
  children: ReactNode;
}

export const GlobalProvider = ({ children }: IGlobalProvider) => {
  const [state, dispatch] = useReducer(reducer, initialState);
  const store = useMemo(() => ({ state, dispatch }), [state, dispatch]);

  return (
    <GlobalContext.Provider value={store}>
     {children}
    </GlobalContext.Provider>
  );
};

export const GlobalConsumer = GlobalContext.Consumer;

export const useGlobal = () => {
  const context = useContext(GlobalContext);
  if (!context) {
    throw new Error("useGlobal must be used after an GlobalContext.Provider");
  }
  return context;
};

```


## Using HOC as a Context API container

Here we have an example of how to abstract the `useGlobal` hook from the context and add some new functions like `requestHandler` who is responsible to make requests and update the state of context.
We can encapsulate all the context updates and make selectors for complex states.

```typescript
import { FC, useCallback } from "react";
import { useGlobal, ACTIONS, GlobalContextProps } from "../contexts/global";

export interface IGlobal extends GlobalContextProps {
  requestHandler: (requestFunction: () => Promise<void>) => void
}

interface IWrappedComponent {
  global: IGlobal;
}

export const withGlobal = (WrappedComponent: FC<IWrappedComponent>) => {
  const HOC = () => {
    const { state, dispatch } = useGlobal();

    const requestHandler = useCallback(
      async (requestFunction) => {
        try {
          dispatch({ type: ACTIONS.IS_LOADING, payload: true });
          return await requestFunction();
        } catch (error) {
          dispatch({ type: ACTIONS.SHOW_ERROR, payload: true });
        } finally {
          dispatch({ type: ACTIONS.IS_LOADING, payload: false });
        }
      },
      [dispatch]
    );

    const props: IGlobal = {
      state,
      dispatch,
      requestHandler
    };

    return <WrappedComponent global={props} />;
  };

  return HOC;
};
```
## HOC usage

To use the HOC above we just call `withGlobal` function and pass the component as a param.

```typescript
import { useCallback, useEffect, useState } from "react";
import { withGlobal, IGlobal } from "../hoc/withGlobal";

interface IProps {
  global: IGlobal;
}

const url = "https://hacker-news.firebaseio.com/v0";

const Page = ({ global: { requestHandler } }: IProps) => {
  const [posts, setPosts] = useState<any>([]);

  const getPosts = useCallback(
    () =>
      requestHandler(async () => {
        const response = await fetch(`${url}/topstories.json`);
        const data = await response.json();
        const requests = data.slice(0, 10).map(async (id: number) => {
          const post = await fetch(`${url}/item/${id}.json`);
          return await post.json();
        });

        const result = await Promise.all(requests);
        setPosts(result);
      }),
    [requestHandler]
  );

  useEffect(() => {
    getPosts();
  }, [getPosts]);

  return (
    <div className="App">
      <h1>Top 10 articles of Hacker News</h1>
      <ul>
        {posts.map((p: any) => (
          <li key={p.id}>
            <a href={p.url} title={p.title}>
              {p.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default withGlobal(Page);
```
In the `Page` component we can use all `withGlobal` features and we can focus on logical and render issues. 
Using HOC in this case gave us a cleaner code and we don't need to worry about updating the global state.

## Example in CodeSandbox

{% codesandbox hoc-with-contextapi-947pe %}  

## Conclusion
HOCs can be very useful to avoid code replication and calling the dispatch function too many times, they can be used as a bridge for components and context.
It's necessary to analyze the performance and if using HOCs in the application makes sense, in the vast majority of cases HOCs are usually a good choice.

In some future post I'll show you how to chain multiple HOCs and avoid props collisions. Thank you for reading.