Writing a React higher-order component with TypeScript

mthmulders picture mthmulders · Apr 28, 2017 · Viewed 13.2k times · Source

I'm writing a React higher-order component (HOC) with TypeScript. The HOC should accept one more prop than the wrapped component, so I wrote this:

type HocProps {
    // Contains the prop my HOC needs
    thingy: number
}
type Component<P> = React.ComponentClass<P> | React.StatelessComponent<P>
interface ComponentDecorator<TChildProps> {
    (component: Component<TChildProps>): Component<HocProps & TChildProps>;
}
const hoc = function<TChildProps>(): (component: Component<TChildProps>) => Component<HocProps & TChildProps) {
    return (Child: Component<TChildProps>) => {
        class MyHOC extends React.Component<HocProps & TChildProps, void> {
            // Implementation skipped for brevity
        }
        return MyHOC;
    }
}
export default hoc;

In other words, hoc is a function that yields the actual HOC. This HOC is (I believe) a function that accepts a Component. Since I don't know in advance what the wrapped component will be, I'm using a generic type TChildProps to define the shape of the props of the wrapped component. The function also returns a Component. The returned component accepts props for the wrapped component (again, typed using the generic TChildProps) and some props it needs for itself (type HocProps). When using the returned component, all of the props (both HocProps and the props for the wrapped Component) should be supplied.

Now, when I attempt to use my HOC, I do the following:

// outside parent component
const WrappedChildComponent = hoc()(ChildComponent);

// inside parent component
render() {
    return <WrappedChild
                thingy={ 42 } 
                // Prop `foo` required by ChildComponent
                foo={ 'bar' } />
}

But I get a TypeScript error:

TS2339: Property 'foo' does not exist on type 'IntrinsicAttributes & HocProps & {} & { children? ReactNode; }'

It seems to me TypeScript is not replacing TChildProps with the shape the of the props needed for ChildComponent. How can I make TypeScript do that?

Answer

ParkourGrip picture ParkourGrip · Dec 3, 2019

A bit late to the party. I like to use the Omit TypeScript utility type to solve this issue. Link to the documentation: https://www.typescriptlang.org/docs/handbook/utility-types.html#omittk

import React, {ComponentType} from 'react';

export interface AdditionalProps {
    additionalProp: string;
}

export function hoc<P extends AdditionalProps>(WrappedComponent: ComponentType<P>) : ComponentType<Omit<P, 'additionalProp'>> {
    const additionalProp = //...
    return props => (
        <WrappedComponent
            additionalProp={additionalProp}
            {...props as any}
        />
    );
}