Oskar Persson
Oskar Persson

Reputation: 6753

Generic higher-order React component gives type errors

I'm trying to write a generic React component that requires props of one of two types (IFoo or IBar) and a component that accepts props of the selected type.

How come the following doesn't work?

Playground

import React from 'react';

interface IFoo {
  x: string;
}

interface IBar {
  x: number;
}

const foo: React.FunctionComponent<IFoo> = (props: IFoo) => {
  console.log("hello from foo!");
  return <div>foo</div>
};

const bar: React.FunctionComponent<IBar> = (props: IBar) => {
  console.log("hello from bar!");
  return <div>bar</div>
};

interface IProps<T> { 
    props: T[];
    Component: React.FunctionComponent<T>;
}


class HigherOrderComponent<T extends IBar | IFoo> extends React.Component<IProps<T>> { 
    render() {
        const { props, Component } = this.props;
        return (<div>
            {props.map(prop => <Component {...prop}/>)};
        </div>)
     }
}

This returns the following error:

Type 'T' is not assignable to type 'IntrinsicAttributes & T & { children?: ReactNode; }'.
  Type 'IFoo | IBar' is not assignable to type 'IntrinsicAttributes & T & { children?: ReactNode; }'.
    Type 'IFoo' is not assignable to type 'IntrinsicAttributes & T & { children?: ReactNode; }'.
      Type 'IFoo' is not assignable to type 'T'.
        'IFoo' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'IFoo | IBar'.
          Type 'T' is not assignable to type 'IntrinsicAttributes'.
            Type 'IFoo | IBar' is not assignable to type 'IntrinsicAttributes'.
              Type 'IFoo' has no properties in common with type 'IntrinsicAttributes'.(2322)

Upvotes: 3

Views: 2340

Answers (2)

Max
Max

Reputation: 2036

You need to create a HOC class dynamically, in order wrap a Component and have correct typings for them

import React from 'react';

interface IFoo {
  x: string;
}

interface IBar {
  x: number;
}

const foo: React.FunctionComponent<IFoo> = (props: IFoo) => {
    console.log("hello from foo!");
  return <div>foo</div>
};

const bar: React.FunctionComponent<IBar> = (props: IBar) => {
    console.log("hello from bar!");
  return <div>bar</div>
};

function createHOC<P extends IFoo | IBar>(Component: React.ComponentType<P>) {
  return class HigherOrderComponent extends React.Component<P> {
    render() {
        console.log(this.props.x)
        return <Component {...this.props} />
     }
  }
}

const WrapperFooComponent = createHOC(foo)

const WrapperBarComponent = createHOC(bar)

Upvotes: 1

Kaca992
Kaca992

Reputation: 2267

Quick fix is to add:

{props.map((prop: T & {}) => <Component {...prop}/>)};

I think the problem is with the spread operator and union type (if there is only one type it works fine). I know TS had problems with that before :(

Hope this helps :)

Upvotes: 7

Related Questions