React Higher-Order Component Patterns in TypeScript

Higher-order components (HOCs) in React are a powerful tool for code reuse between components. However, a common complaint of developers using TypeScript is that they are difficult to set types for.

This article assumes knowledge of HOCs, and will go through three examples of HOC patterns of increasing complexity, showing how, with a little effort (and a few gotchas and workarounds!), they can be typed without turning to any or type-casting.

The components used in the examples will be extremely basic, so they do not necessarily serve as good examples of HOC usage and best practices; the main aim of this article is to demonstrate how they can be typed effectively.


The component that we’ll be wrapping is a Hello component, which takes in style and name props and outputs a greeting for the specified name:

Pattern 1: Passing Through Props

The most basic HOC to type in TypeScript is one which does not expose its own props and does not hide the props of the unwrapped component — it simply passes through the same props that are passed to it, possibly modifying them en route. An example might be a HOC which enforces that styles are set for a component via the style prop, a simple example of this being the following withBlueBackground HOC, which we’ll use throughout this article, and, as the name suggests, ensures the background colour is blue for the wrapped component:

Note: this could also be expressed as a function:

function withBlueBackground<P extends WithBlueBackgroundProps>(
UnwrappedComponent: React.ComponentType<P>
) {
return class WithBlueBackground extends React.Component<P> {
...
}
}

There are a few things going on in the example:

const withBlueBackground = <P extends WithBlueBackgroundProps>(

Here, we are using a generic to specify that the unwrapped component passed in as an parameter to the HOC function must support the props of the HOC in addition to its own props. In this instance, we handle this in the Hello component by simply duplicating the style prop:

interface HelloProps {
style?: React.CSSProperties;
name: string;
}

The type variable P is then used to specify the props for the unwrapped component to be passed into the HOC:

UnwrappedComponent: React.ComponentType<P>

React.ComponentType<P> is just an alias for React.StatelessComponent<P> | React.Component<P>, which would allow either a stateless functional component or a class component to be passed into the HOC.

Object.assign() is then used to override any styles that are passed into the component with the blue background colour:

style={Object.assign({}, this.props.style, {
backgroundColor: 'blue',
})}

You may be wondering why object rest/spread is not being used here, despite TypeScript supporting it since version 2.1. This is the first gotcha, and the reason for this is that TypeScript has some object rest/spread issues with React which are currently being addressed in a long-standing pull request. You will also face this same issue if you want to destructure the props using object rest:

In terms of usage, the HOC can then be used as if it were a plain JavaScript HOC, with the added bonus that the wrapped component is statically typed:

Pattern #2: Adding Props

Some HOCs add props to the unwrapped component, such as a HOC which handles the display of a loading state for a component and allows the consumer of the wrapped component to set a loading prop on it to true or false.

Building on our own example, we will add a shade prop to the withBlueBackground HOC, which sets the shade of blue colour to be used for the background:

The main thing to note here is that the props interfaces have now been split into two:

interface InjectedBlueBackgroundProps {
style?: React.CSSProperties;
}
interface WithBlueBackgroundProps {
shade?: ColorShade;
}

The point of this is to differentiate between the props which are to be injected into the unwrapped component, and the props which will be exposed when the component is wrapped, but not passed through to the unwrapped component. InjectedBlueBackgroundProps is now used for the generic:

const withBlueBackground = <P extends InjectedBlueBackgroundProps>(

As with pattern #1, this just ensures that the wrapped component has the injected props in its own props. Despite InjectedBlueBackgroundProps only containing style, it is a common (and good) practice to import the interface into the unwrapped component and extend it:

import { InjectedBlueBackgroundProps } from './withBlueBackground';
interface HelloProps extends InjectedBlueBackgroundProps {
style?: React.CSSProperties;
name: string;
}

This will be more useful when additional props are injected, and ones which aren’t standard React props such as style.

As for the HOC’s own props (WithBlueBackgroundProps) these are combined with the wrapped component’s props using a type intersection operator (&) :

class WithBlueBackground extends React.Component<
P & WithBlueBackgroundProps
> {
...
}

This will then allow the shade prop to be used by the consumer of the HOC, and, due to it not being included in the injected props, you will receive an error if you attempt to pass it down to the unwrapped component:

For anyone with a keen eye, you may have noticed the following in the HOC:

{...this.props}

This means that shade will be passed through to the unwrapped component, though you will receive a compilation error if you attempt to access it in there. Having to spread the props is due to the TypeScript issue around object rest/spread mentioned in pattern #1, and though it’s unlikely to cause problems for you, you should be aware of it, and ultimately the way forward will be to switch to use object rest and destructuring to extract out any props you don’t want passed through when the issue is fixed.

Pattern #3: Removing Props

The most difficult HOC to set types for with TypeScript is one which takes away/hides props from the unwrapped component when it is wrapped. An example for this pattern would be a HOC which passes a theme to an unwrapped component, but does not expose the theme prop to the consumer of the wrapped component; the theme handling is completely hidden away from the end-user.

To demonstrate this with the withBlueBackground HOC, we will change it to require a component passed in that accepts a backgroundColor prop rather than setting the colour via style:

Given we changed HelloProps to extend InjectedBlueBackgroundProps in pattern #2, no changes will need to be made to it, but the Hello component will now need to be changed to handle backgroundColor how it sees fit.

However, there is a problem with the HOC: it is still returning the props (P) of the unwrapped component, which now includes backgroundColor:

const withBlueBackground = <P extends InjectedBlueBackgroundProps>(
UnwrappedComponent: React.ComponentType<P>
) =>
class WithBlueBackground extends React.Component<
P & WithBlueBackgroundProps
> {
...
}

This means that when the wrapped component is used, you will receive a compilation error as it is expecting backgroundColor to be passed to it:

One way to solve this is to change the HOC to use type intersection in its UnwrappedComponent parameter, rather than extension in the generic:

const withBlueBackground = <P>(
UnwrappedComponent: React.ComponentType<
P & InjectedBlueBackgroundProps
>
) => {
...
}

However, this raises another gotcha:

This is a known issue that is unlikely to be fixed, and is due to the fact the compiler cannot differentiate between JSX and generics in .tsx files in this scenario. There are a couple of workarounds though, one being to replace the fat arrow function with a function declaration (as mentioned in the Github issue), or another more hacky fix is to change the generic to <P extends object>.

For the intersection approach to work, it will also need intersection and more explicit typing in the Hello component:

Here, HelloProps no longer extends InjectedBlueBackgroundProps, it is combined using type intersection in the component itself, so that the Hello component’s own props are separated from the injected ones. They can then be used to specify which props should be used for the wrapped component:

withBlueBackground<HelloProps>(Hello);

The majority of typings for libraries which use HOCs use this approach or something similar, which forces the consumer to be more explicit about the types they’d like to use with it, and the issues with not specifying the types can be easily missed until the wrapped component is used.

Type Subtraction

Recent developments in TypeScript now allow developers to avoid the approach detailed above, and instead let the types be handled internally by the HOC.

Type subtraction has long been discussed, which would allow a HOC to remove the injected props from its own props, meaning that the consumer does not have to be explicit about the types in their unwrapped component and worry about intersection vs extension. Though no first-class solution has been released yet in TypeScript, there are a few implementations available in user land. Omit, which was initially devised by language creator Anders Hejlsberg in a comment on Github, and later removed (likely due to it having issues in some scenarios/versions), can be used to subtract props:

Here, Omit is used to remove all of the injected properties from the higher order component:

Omit<P, keyof InjectedBlueBackgroundProps> & WithBlueBackgroundProps

You may have also noticed the nice side-effect that Omit has — it now allows the props to be destructured correctly:

const { shade, ...props } = this.props;

If you’d like to get the types from an npm package, Piotr Witek’s utility-types package contains Diff, Omit and other useful helpers, such as Subtract, which is even more suited to this task as you don’t have to remember to use keyof as with Omit:

Until type subtraction is officially supported in TypeScript, these types provide an excellent stop-gap solution, and despite the fact they could potentially break between TypeScript versions and in more complex scenarios, they take away a lot of confusion around typing for HOCs, particularly for the consumer, and if they do break, you will receive compilation errors at least.

Update: Exclude has been added to TypeScript for this purpose and looks set to be released in TypeScript 2.8:

The Exclude type is a proper implementation of the Diff type suggested here #12215 (comment). We've used the name Exclude to avoid breaking existing code that defines a Diff, plus we feel that name better conveys the semantics of the type. We did not include the Omit<T, K> type because it is trivially written as Pick<T, Exclude<keyof T, K>>.

Even though typing HOCs can be difficult to get right in TypeScript, it can be done, and with TypeScript addressing a few of the issues, it’s likely they will become easier to write in the future.

In general, if you are writing a lot of HOCs in your project, it may be a sign that you are overusing them and overcomplicating things, so I’d keep them to a minimum and have a good, clear example of one that you can point developers towards as an example, ideally one that uses pattern 3 and type subtraction.

If you still find HOCs too difficult, an alternative could be simply extracting out shared functionality into utility functions, or for more complex scenarios, using render props in their place, which are much simpler to type.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.