Higher-Order Components in React Hooks era
Tags: react, javascript, webdev, tutorial
Published: 2022-02-27 22:14:13


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kakn76np1t0f8whw9fz0.png)


Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

---

Is it true that React hooks made higher-order components obsolete? And the only use case for those is to be a remnant of the past in some existential legacy corners of our apps? And what is a higher-order component anyway? Why did we need them in the first place?

Answering those questions and building a case that higher-order components are still useful even in modern apps for certain types of tasks.

But let's start from the beginning.

## What is a higher-order component?

According to <a href="https://reactjs.org/docs/higher-order-components.html">React docs</a>, it’s an advanced technique to re-use components logic that is used for cross-cutting concerns, if that description means anything to you (for me not so much 🙂).

In English, it’s just a function, that accepts a component as one of its arguments, messes with it, and then returns back its changed version. The simplest variant of it, that does nothing, is this:

```tsx
// accept a Component as an argument
const withSomeLogic = (Component) => {
  // do something

  // return a component that renders the component from the argument
  return (props) => <Component {...props} />;
};
```

The key here is the return part of the function - it’s just a component, like any other component. And similar to the <a href="https://www.developerway.com/posts/react-component-as-prop-the-right-way">render props pattern</a>, we need to pass props to the returned component, otherwise, they will be swallowed.

And then, when it’s time to use it, it would look like this:

```tsx
const Button = ({ onClick }) => <button onClick={func}>Button</button>;
const ButtonWithSomeLogic = withSomeLogic(Button);
```

You pass your `Button` component to the function, and it returns the new `Button`, which includes whatever logic is defined in the higher-order component. And then this button can be used as any other button:

```tsx
const SomePage = () => {
  return (
    <>
      <Button />
      <ButtonWithSomeLogic />
    </>
  );
};
```

If we want to create a mental map of what goes where it could look something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39u0phvfnlom8jhwnmzn.png)

Play around with those examples <a href="https://codesandbox.io/s/hocs-intro-6jqmju?file=/src/App.tsx">in codesandbox.</a>

Before the introduction of hooks, higher-order components were widely used for accessing context and any external data subscriptions. <a href="https://react-redux.js.org/api/connect">Redux connect</a> or <a href="https://v5.reactrouter.com/web/api/withRouter">react-router’s</a> `withRouter` functions are higher-order components: they accept a component, inject some props into it, and return it back.

```tsx
// location is injected by the withRouter higher-order component
// would you guessed that by the look at this component alone?
const SomeComponent = ({ location }) => {
  return <>{location}</>;
};

const ComponentWithRouter = withRouter(SomeComponent);
```

As you can see, higher-order components are quite complicated to write and to understand. So when <a href="https://reactjs.org/docs/hooks-intro.html">the hooks</a> were introduced, no wonder everyone switched to them.

Now, instead of creating complicated mental maps of which prop goes where and trying to figure out how `location` ended up in props, we can just write:

```tsx
const SomeComponent = () => {
  // we see immediately where location is coming from
  const { location } = useRouter();

  return <>{location}</>;
};
```

Everything that is happening in the component can be read from top to bottom and the source of all the data is obvious, which significantly simplifies debugging and development.

And while hooks probably replaced 90% of shared logic concerns and 100% of use-cases for accessing context, there are still at least three types of functionality, where higher-order components could be useful.

Let’s take a look at those.

## First: enhancing callbacks and React lifecycle events

Imagine you need to send some sort of advanced logging on some callbacks. When you click a button, for example, you want to send some logging events with some data. How would you do it with hooks? You’d probably have a `Button` component with an `onClick` callback:

```tsx
type ButtonProps = {
  onClick: () => void;
  children: ReactNode;
}

const Button = ({ onClick }: { onClick }: ButtonProps) => {
  return <button onClick={onClick}>{children}</button>
}
```

And then on the consumer side, you’d hook into that callback and send logging event there:

```tsx
const SomePage = () => {
  const log = useLoggingSystem();

  const onClick = () => {
    log('Button was clicked');
  };

  return <Button onClick={() => onClick}>Click here</Button>;
};
```

And that is fine if you want to fire an event or two. But what if you want your logging events to be consistently fired across your entire app, whenever the button is clicked? We probably can bake it into the `Button` component itself.

```tsx
const Button = ({ onClick }: { onClick }: ButtonProps) => {
  const log = useLoggingSystem();

  const onButtonClick = () => {
    log('Button was clicked')
    onClick();
  }

  return <button onClick={() => onClick()}>{children}</button>
}
```

But then what? For proper logs you’d have to send some sort of data as well. We surely can extend the `Button` component with some `loggingData` props and pass it down:

```tsx
const Button = ({ onClick, loggingData }: { onClick, loggingData }: ButtonProps) => {
  const onButtonClick = () => {
    log('Button was clicked', loggingData)
    onClick();
  }
  return <button onClick={() => onButtonClick()}>{children}</button>
}
```

But what if you want to fire the same events when the click has happened on other components? `Button` is usually not the only thing people can click on in our apps. What if I want to add the same logging to a `ListItem` component? Copy-paste exactly the same logic there?

```tsx
const ListItem = ({ onClick, loggingData }: { onClick, loggingData }: ListItemProps) => {
  const onListItemClick = () => {
    log('List item was clicked', loggingData)
    onClick();
  }
  return <Item onClick={() => onListItemClick()}>{children}</Item>
}
```

Too much copy-pasta and prone to errors and someone forgetting to change something in my taste.

What I want, essentially, is to encapsulate the logic of “something triggered `onClick` callback - send some logging events” somewhere, and then just re-used it in any component I want, without changing the code of those components in any way.

And this is the first use case where the hooks are no use, but higher-order components could come in handy.

### Higher-order component to enhance onClick callback

Instead of copy-pasting the “click happened → log data” logic everywhere, I can just create a `withLoggingOnClick` function, that:

- accepts a component as an argument
- intercepts its onClick callback
- sends the data that I need to the whatever external framework is used for logging
- returns the component with onClick callback intact for further use

It would look something like this:

```tsx
type Base = { onClick: () => void };

// just a function that accepts Component as an argument
export const withLoggingOnClick = <TProps extends Base>(Component: ComponentType<TProps>) => {
  return (props: TProps) => {
    const onClick = () => {
      console.log('Log on click something');
      // don't forget to call onClick that is coming from props!
      // we're overriding it below
      props.onClick();
    };

    // return original component with all the props
    // and overriding onClick with our own callback
    return <Component {...props} onClick={onClick} />;
  };
};
```

And now I can just add it to **any** component that I want. I can have a `Button` with logging baked in:

```tsx
export const ButtonWithLoggingOnClick = withLoggingOnClick(SimpleButton);
```

Or use it in the list item:

```tsx
export const ListItemWithLoggingOnClick = withLoggingOnClick(ListItem);
```

Or any other component that has `onClick` callback that I want to track. Without a single line of code changed in either `Button` or `ListItem` components!

### Adding data to the higher-order component

Now, what’s left to do, is to add some data from the outside to the logging function. And considering that higher-order component is nothing more than just a function, we can do that easily. Just need to add some other arguments to the function, that’s it:

```tsx
type Base = { onClick: () => void };
export const withLoggingOnClickWithParams = <TProps extends Base>(
  Component: ComponentType<TProps>,
  // adding some params as a second argument to the function
  params: { text: string },
) => {
  return (props: TProps) => {
    const onClick = () => {
      // accessing params that we passed as an argument here
      // everything else stays the same
      console.log('Log on click: ', params.text);
      props.onClick();
    };

    return <Component {...props} onClick={onClick} />;
  };
};
```

And now, when we wrap our button with higher-order component, we can pass the text that we want to log:

```tsx
const ButtonWithLoggingOnClickWithParams = withLoggingOnClickWithParams(SimpleButton, { text: 'button component' });
```

On the consumer side, we’d just use this button as a normal button component, without worrying about the logging text:

```tsx
const Page = () => {
  return <ButtonWithLoggingOnClickWithParams onClick={onClickCallback}>Click me</ButtonWithLoggingOnClickWithParams>;
};
```

But what if we actually **want** to worry about this text? What if we want to send different texts in different contexts of where the button is used? We wouldn’t want to create one million wrapped buttons for every use case.

Also very easy to solve: instead of passing that text as function’s argument, we can inject it as a prop to the resulting button. The code would look like this:

```tsx
type Base = { onClick: () => void };
export const withLoggingOnClickWithProps = <TProps extends Base>(Component: ComponentType<TProps>) => {
  // our returned component will now have additional logText prop
  return (props: TProps & { logText: string }) => {
    const onClick = () => {
      // accessing it here, as any other props
      console.log('Log on click: ', props.logText);
      props.onClick();
    };

    return <Component {...props} onClick={onClick} />;
  };
};
```

And then use it like this:

```tsx
const Page = () => {
  return (
    <ButtonWithLoggingOnClickWithProps onClick={onClickCallback} logText="this is Page button">
      Click me
    </ButtonWithLoggingOnClickWithProps>
  );
};
```

See the <a href="https://codesandbox.io/s/hocs-buttons-m45hm4?file=/src/hocs/with-logging-on-click.tsx">codesandbox with all the examples</a>.

### Sending data on mount instead of click

We are not limited to clicks and callbacks here. Remember, those are just components, we can do whatever we want and need 🙂 We can use everything React has to offer. For example, we can send those logging events when a component is mounted:

```tsx
export const withLoggingOnMount = <TProps extends unknown>(Component: ComponentType<TProps>) => {
  return (props: TProps) => {
    // no more overriding onClick, just adding normal useEffect
    useEffect(() => {
      console.log('log on mount');
    }, []);

    // just passing props intact
    return <Component {...props} />;
  };
};
```

And exactly the same story as with `onClick` for adding data via arguments or props. Not going to copy-paste it here, see it in <a href="https://codesandbox.io/s/hocs-buttons-m45hm4?file=/src/hocs/with-logging-on-mount.tsx:58-305">the codesandbox</a>.

We can even go wild and combine all of those higher-order components:

```tsx
export const SuperButton = withLoggingOnClick(
  withLoggingOnClickWithParams(
    withLoggingOnClickWithProps(
      withLoggingOnMount(withLoggingOnMountWithParams(withLoggingOnMountWithProps(SimpleButton), { text: 'button component' })),
    ),
    { text: 'button component' },
  ),
);
```

We shouldn’t do this of course though 😅 If something is possible, it doesn’t always mean it’s a good idea. Imagine trying to trace which props come from where, when debugging time comes. If we really need to combine a few higher-order components into one, we can be at least a bit more specific about it:

```tsx
const ButtonWithLoggingOnClick = withLoggingOnClick(SimpleButton);
const ButtonWithLoggingOnClickAndMount = withLoggingOnMount(ButtonWithLoggingOnClick);
// etc
```

## Second: intercepting DOM events

Another very useful application of higher-order components is intercepting various DOM events. Imagine, for example, you implement some sort of keyboard shortcuts functionality on your page. When specific keys are pressed, you want to do various things, like open dialogs, creating issues, etc. You’d probably add an event listener to window for something like this:

```tsx
useEffect(() => {
  const keyPressListener = (event) => {
    // do stuff
  };

  window.addEventListener('keypress', keyPressListener);

  return () => window.removeEventListener('keypress', keyPressListener);
}, []);
```

And then, you have various parts of your app, like modal dialogs, dropdown menus, drawers, etc, where you want to block that global listener while the dialog is open. If it was just one dialog, you can manually add `onKeyPress` to the dialog itself and there do `event.stopPropagation()` for that:

```tsx
export const Modal = ({ onClose }: ModalProps) => {
  const onKeyPress = (event) => event.stopPropagation();

  return <div onKeyPress={onKeyPress}>...// dialog code</div>;
};
```

But the same story as with `onClick` logging - what if you have multiple components where you want to see this logic?

What we can do here, is again implement a higher-order component. This time it will accept a component, wrap it in a div with onKeyPress callback attached, and return the component unchanged.

```tsx
export const withSupressKeyPress = <TProps extends unknown>(Component: ComponentType<TProps>) => {
  return (props: TProps) => {
    const onKeyPress = (event) => {
      event.stopPropagation();
    };

    return (
      <div onKeyPress={onKeyPress}>
        <Component {...props} />
      </div>
    );
  };
};
```

That is it! Now we can just use it everywhere:

```tsx
const ModalWithSupressedKeyPress = withSupressKeyPress(Modal);
const DropdownWithSupressedKeyPress = withSupressKeyPress(Dropdown);
// etc
```

One Important thing to note here: focus management. In order for the above code to actually work, you need to make sure that your dialog-type components move focus to the opened part when they are open. But this is a completely different conversation on focus management, maybe next time.

For the purpose of the example, we can just manually include auto-focus in the modal itself:

```tsx
const Modal = () => {
  const ref = useRef<HTMLDivElement>();

  useEffect(() => {
    // when modal is mounted, focus the element to which the ref is attached
    if (ref.current) ref.current.focus();
  }, []);

  // adding tabIndex and ref to the div, so now it's focusable
  return <div tabIndex={1} ref={ref}>
    <!-- modal code -->
  </div>
}
```

Play around with it in <a href="https://codesandbox.io/s/hocs-modal-mgbq6x?file=/src/hocs/with-supress-key-press.tsx">the codesandbox</a>.

## Third: context selectors

The final and very interesting use case for higher-order components: selectors-like functionality for React context. As we know, when context value changes, it will cause re-renders of all context consumers, regardless of whether their particular part of the state was changed or not. (And if you didn’t know about it, here’s the article for you: <a href="https://www.developerway.com/posts/how-to-write-performant-react-apps-with-context">How to write performant React apps with Context</a>).

Let’s implement some context and form first, before jumping into higher-order components.

We’ll have Context with `id` and `name` and API to change those:

```tsx
type Context = {
  id: string;
  name: string;
  setId: (val: string) => void;
  setName: (val: string) => void;
};

const defaultValue = {
  id: 'FormId',
  name: '',
  setId: () => undefined,
  setName: () => undefined,
};

const FormContext = createContext<Context>(defaultValue);

export const useFormContext = () => useContext(FormContext);

export const FormProvider = ({ children }: { children: ReactNode }) => {
  const [state, setState] = useState(defaultValue);

  const value = useMemo(() => {
    return {
      id: state.id,
      name: state.name,
      setId: (id: string) => setState({ ...state, id }),
      setName: (name: string) => setState({ ...state, name }),
    };
  }, [state]);

  return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
};
```

And then some form with `Name` and `Countries` components

```tsx
const Form = () => {
  return (
    <form css={pageCss}>
      <Name />
      <Countries />
    </form>
  );
};

export const Page = () => {
  return (
    <FormProvider>
      <Form />
    </FormProvider>
  );
};
```

Where in `Name` component we’ll have an input that changes the value of `Context`, and `Countries` just use the `id` of the form to fetch the list of countries (not going to implement the actual fetch, not important for the example:

```tsx
const Countries = () => {
  // using only id from context here
  const { id } = useFormContext();

  console.log("Countries re-render");
  return (
    <div>
      <h3>List on countries for form: {id}</h3>
      <ul>
        <li>Australia</li>
        <li>USA</li>
        <!-- etc -->
      </ul>
    </div>
  );
};
```

```tsx
const Name = () => {
  // using name and changing it here
  const { name, setName } = useFormContext();

  return <input onChange={(event) => setName(event.target.value)} value={name} />;
};
```

Now, every time we type something in the name input field, we’ll update the context value, which will cause re-render of all components that use context, including Countries. And this can’t be solved by extracting this value into a hook and memoising it: hooks always re-render (<a href="https://www.developerway.com/posts/why-custom-react-hooks-could-destroy-your-app-performance">Why custom react hooks could destroy your app performance</a>).

There are other ways to deal with it of course, if this behaviour causes performance concerns, like memoising parts of render tree or splitting Context into different providers (see those articles that describe those techniques: <a href="https://www.developerway.com/posts/how-to-write-performant-react-apps-with-context">How to write performant React apps with Context</a> and <a href="https://www.developerway.com/posts/how-to-write-performant-react-code">How to write performant React code: rules, patterns, do's and don'ts</a>).

But big disadvantage of all the techniques above, is that they are not shareable and need to be implemented on a case-by-case basis. Wouldn’t it be nice, if we had some select-like functionality, that we can use to extract this `id` value safely in any component, without significant refactorings and `useMemo` all over the app?

Interestingly enough, we can implement something like this with higher-order components. And the reason for this is that components have one thing that hooks don’t give us: they can memoise things and stop the chain of re-renders going down to children. Basically, this will give us what we want:

```tsx
export const withFormIdSelector = <TProps extends unknown>(
  Component: ComponentType<TProps & { formId: string }>
) => {
  const MemoisedComponent = React.memo(Component) as ComponentType<
    TProps & { formId: string }
  >;

  return (props: TProps) => {
    const { id } = useFormContext();

    return <MemoisedComponent {...props} formId={id} />;
  };
};
```

and then we can just create `CountriesWithFormIdSelector` component:

```tsx
// formId prop here is injected by the higher-order component below
const CountriesWithFormId = ({ formId }: { formId: string }) => {
  console.log("Countries with selector re-render");
  return (
     <-- code is the same as before -->
  );
};

const CountriesWithFormIdSelector = withFormIdSelector(CountriesWithFormId);
```

And use it in our form:

```tsx
const Form = () => {
  return (
    <form css={pageCss}>
      <Name />
      <CountriesWithFormIdSelector />
    </form>
  );
};
```

<a href="https://codesandbox.io/s/hocs-context-lwudbb?file=/src/page.tsx">Check it out in the codesandbox</a>. Pay special attention of the
console output when typing in the input - CountriesWithFormIdSelector component doesn’t re-render!

### Generic React context selector

`withFormIdSelector` is fun and could work for small context-based apps. But wouldn’t it be nice to have it as something generic? So that we don’t have to implement a custom selector for every state property.

No problem when some creative hackery is involved! Check it out, selector itself:

```tsx
export const withContextSelector = <TProps extends unknown, TValue extends unknown>(
  Component: ComponentType<TProps & Record<string, TValue>>,
  selectors: Record<string, (data: Context) => TValue>,
): ComponentType<Record<string, TValue>> => {
  // memoising component generally for every prop
  const MemoisedComponent = React.memo(Component) as ComponentType<Record<string, TValue>>;

  return (props: TProps & Record<string, TValue>) => {
    // extracting everything from context
    const data = useFormContext();

    // mapping keys that are coming from "selectors" argument
    // to data from context
    const contextProps = Object.keys(selectors).reduce((acc, key) => {
      acc[key] = selectors[key](data);

      return acc;
    }, {});

    // spreading all props to the memoised component
    return <MemoisedComponent {...props} {...contextProps} />;
  };
};
```

and then use it with components:

```tsx
// props are injected by the higher order component below
const CountriesWithFormId = ({ formId, countryName }: { formId: string; countryName: string }) => {
  console.log('Countries with selector re-render');
  return (
    <div>
      <h3>List of countries for form: {formId}</h3>
      Selected country: {countryName}
      <ul>
        <li>Australia</li>
        <li>USA</li>
      </ul>
    </div>
  );
};

// mapping props to selector functions
const CountriesWithFormIdSelector = withContextSelector(CountriesWithFormId, {
  formId: (data) => data.id,
  countryName: (data) => data.country,
});
```

And that’s it! we basically implemented mini-Redux on context, even with proper `mapStateToProps` functionality 🙂 Check it out in <a href="https://codesandbox.io/s/hocs-context-generic-selector-wdijzp?file=/src/page.tsx">the codesandbox</a>.

That is it for today! Hope higher-order components are not some terrifying legacy goblins now, but something you can put to good use even in modern apps. Let’s re-cap the use cases for those:

- to enhance callbacks and React lifecycle events with additional functionality, like sending logging or analytics events
- to intercept DOM events, like blocking global keyboard shortcuts when a modal dialog is open
- to extract a piece of Context without causing unnecessary re-renders in the component

May the peace and love be with you ✌🏼


...

Originally published at [https://www.developerway.com](https://www.developerway.com). The website has more articles like this 😉

[Subscribe to the newsletter](https://www.developerway.com), [connect on LinkedIn](https://www.linkedin.com/in/adevnadia/) or [follow on Twitter](https://twitter.com/adevnadia) to get notified as soon as the next article comes out.