Writing a React higher-order component with TypeScript

If what you're asking for is if it's possible to define a HOC that can add a new prop, let's say "thingy", to a component without modifying that component's props definition to include "thingy" I think that's impossible.

That's because at some point in the code you'll end up with:

render() {
    return (
        <WrappedComponent thingy={this.props.thingy} {...this.props}/>
    );
}

And that will always throw an error if WrappedComponent does not include thingy in its props definition. The child has to know what it receives. Off hand, I can't think of a reason for passing a prop to a component that doesn't know about to it anyway. You wouldn't be able to reference that prop in the child component without an error.

I think the trick is to define the HOC as a generic around the props of the child and then to just include your prop thingy or whatever in that child's interface explicitly.

interface HocProps {
    // Contains the prop my HOC needs
    thingy: number;
}

const hoc = function<P extends HocProps>(
    WrappedComponent: new () => React.Component<P, any>
) {
    return class MyHOC extends React.Component<P, any> {
        render() {
            return (
                <WrappedComponent {...this.props}/>
            );
        }
    }
}
export default hoc;

// Example child class

// Need to make sure the Child class includes 'thingy' in its props definition or
// this will throw an error below where we assign `const Child = hoc(ChildClass)`
interface ChildClassProps {
    thingy: number;
}

class ChildClass extends React.Component<ChildClassProps, void> {
    render() {
        return (
            <h1>{this.props.thingy}</h1>
        );
    }
}

const Child = hoc(ChildClass);

Now of course this example HOC doesn't really do anything. Really HOC's should be doing some sort of logic to provide a value for the child prop. Like for example maybe you have a component that displays some generic data that gets updated repeatedly. You could have different ways it gets updated and create HOC's to separate that logic out.

You have a component:

interface ChildComponentProps {
    lastUpdated: number;
    data: any;
}

class ChildComponent extends React.Component<ChildComponentProps, void> {
    render() {
        return (
            <div>
                <h1>{this.props.lastUpdated}</h1>
                <p>{JSON.stringify(this.props.data)}</p>
            </div>
        );
    }
}

And then an example HOC that just updates the child component on a fixed interval using setInterval might be:

interface AutoUpdateProps {
    lastUpdated: number;
}

export function AutoUpdate<P extends AutoUpdateProps>(
    WrappedComponent: new () => React.Component<P, any>,
    updateInterval: number
) {
    return class extends React.Component<P, any> {
        autoUpdateIntervalId: number;
        lastUpdated: number;

        componentWillMount() {
            this.lastUpdated = 0;
            this.autoUpdateIntervalId = setInterval(() => {
                this.lastUpdated = performance.now();
                this.forceUpdate();
            }, updateInterval);
        }

        componentWillUnMount() {
            clearInterval(this.autoUpdateIntervalId);
        }

        render() {
            return (
                <WrappedComponent lastUpdated={this.lastUpdated} {...this.props}/>
            );
        }
    }
}

Then we could create a component that updates our child once every second like this:

const Child = AutoUpdate(ChildComponent, 1000);

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}
        />
    );
}