How to implement a Higher-order component in React with TypeScript

In TypeScript, typing a HoC can be a little bit confusing, especially if you read some of the blog posts out there.

I came across a situation at work in which I needed to use a React Hook in a class-based component. My initial instinct was to rewrite from a class component to a function component, however upon further inspection I realised it had 1100 lines of code, with relatively complicated lifecycle methods.

I decided to wrap the component with a functional HoC that called the Hook, and passed the result down to the component as a prop.

In normal JavaScript, it isn't too complicated, you'd do something like this:

import React, { useState } from 'react';
export function withExtraInfo(WrappedComponent) {
const [extraInfo, setExtraInfo] = useState('');
const ComponentWithExtraInfo = (props) => {
return <WrappedComponent {...props} extraInfo={extraInfo} />;
};
return ComponentWithExtraInfo;
}

If you tried to run TypeScript against the code above, you'd need to fix a few things:

  1. Both WrappedComponent and props have an implicit any type
  2. Make the function generic

Here's how we'd do that:

import React, { useState } from 'react';
// First we need to add a type to let us extend the incoming component.
type ExtraInfoType = {
extraInfo: string;
};
// Mark the function as a generic using P (or whatever variable you want)
export function withExtraInfo<P>(
// Then we need to type the incoming component.
// This creates a union type of whatever the component
// already accepts AND our extraInfo prop
WrappedComponent: React.ComponentType<P & ExtraInfoType>
) {
const [extraInfo, setExtraInfo] = useState('');
setExtraInfo('important data.');
const ComponentWithExtraInfo = (props: P) => {
// At this point, the props being passed in are the original props the component expects.
return <WrappedComponent {...props} extraInfo={extraInfo} />;
};
return ComponentWithExtraInfo;
}

You'll probably notice we marked withExtraInfo as a generic using <P>. For more information, see the TypeScript Handbook.

To wrap things up, you'll want to add a displayName to your HoC, which I've left as an exercise for the reader.

(Shameless plug for the useEffect book I wrote below)

Tired of infinite re-renders when using useEffect?

A few years ago when I worked at Atlassian, a useEffect bug I wrote took down part of Jira for roughly one hour.

Knowing thousands of customers can't work because of a bug you wrote is a terrible feeling. To save others from making the same mistakes, I wrote a single resource that answers all of your questions about useEffect, after teaching it here on my blog for the last couple of years. It's packed with examples to get you confident writing and refactoring your useEffect code.

In a single afternoon, you'll learn how to fetch data with useEffect, how to use the dependency array, even how to prevent infinite re-renders with useCallback.

Master useEffect, in a single afternoon.

useEffect By Example's book cover