score:1

EDIT: After some changes to your code, it was only a wrong constraint T in your withTd function.

// I needed to change the constraint on T, but you may adapt with your own needs
export const withTd = <T extends FooProps<WithTdProps>>(
  TableElement: React.ComponentType<T>
): React.SFC<T> => (props: T) => (
  <td>
    <TableElement {...props} />
  </td>
)

// Explicitly typed constructor
// Removed after EDIT
//const FooW = Foo as new (props: FooProps<WithTdProps>) => Foo<WithTdProps>

// Inferred as React.StatelessComponent<FooProps<WithTdProps>>
const FooWithTd = withTd(Foo)

No longer relevant after EDIT :

You may find more information at this issue https://github.com/Microsoft/TypeScript/issues/3960

score:1

Just stumbled upon this as well and thought I'd share what I came up with in the end.

Building on what @rico-kahler provided, my approach mapped to your code would be

export const FooWithTd = withTd(Foo) as <T>(props: FooProps<T>) => React.ReactElement<FooProps<T>>;

which you can then use like this

export class Bar extends React.Component<{}> {
  public render() {
    return (
      <FooWithTd<number> />
    );
  }
}

In my case, I have defaultProps as well and I inject props by ways of another HOC, the more complete solution would look like this:

type DefaultProps = "a" | "b";
type InjectedProps = "classes" | "theme";
type WithTdProps<T> = Omit<FooProps<T>, DefaultProps | InjectedProps> & Partial<FooProps<T> & { children: React.ReactNode }>;
export const FooWithTd = withTd(Foo) as <T>(props: WithTdProps<T>) => React.ReactElement<WithTdProps<T>>;

score:1

Workaround: simple case

If your component's type parameter is used only for passing it to props, and users of the component do not expect it having any functionality beyond just passing props and rendering, you can explicitly hard-cast the result of your hoc(...args)(Component) to React's functional component type, like this:

import React, {ReactElement} from 'react';

class MyComponent<T> extends React.Component<MyProps<T>> { /*...*/ }

const kindaFixed = myHoc(...args)(MyComponent) as unknown as <T>(props: MyProps<T>) => ReactElement;

Workaround: more complex and with some runtime costs

You can use fabric-like function, supposed here:

class MyComponent<T> extends React.Component<MyProps<T>> { /*...*/ }

export default function MyComponentFabric<T>() {
    return hoc(...args)(MyComponent as new(props: MyProps<T>) => MyComponent<T>);
}

This one will require you to create new version of wrapped component for each type you use it with:

import MyComponentFabric from '...whenever';

const MyComponentSpecificToStrings = MyComponentFabric<string>();

It will allow you to access all public instance fields and methods of your component.

Summary

I faced this issue when tried to use connect from react-redux on my ExampleGenericComponent<T>. Unfortunatelly, it cannot be fixed properly until TypeScript will support HKT, and any HOC you use will update its typings respecting this feature.

There is possibly no correct solution (at least for now) for usages beyond just rendering, when you need to access component instance fields and methods. By 'correct' I mean 'without ugly explicit typecasts', and 'with no runtime cost'.

One thing you can try is to split your class-component into two components, one that will be used with HOC, and other that will provide fields and methods that you need.

score:3

You can wrap your component which is created from a HOC into another component. It would look something like this:

class FooWithTd<T> extends React.Component<SomeType<T>> {
     private Container: React.Component<SomeType<T> & HOCResultType>; 

     constructor(props:SomeType<T>){
          super(props);
          this.Container = withTd(Foo<T>);
     }

     render() {
          return <this.Container {...this.props} />;
     }
}

Remember, you probably don't want the HOC inside your render function because it means that the component will be recreated every each render.

score:4

Thanks for asking this question. I just figured out a way to specify a type parameter to a component after wrapping it with an HOC and I thought I'd share.

import React from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
import { RemoveProps } from '../helpers/typings';

const styles = {
  // blah
};

interface Props<T> {
  classes: any;
  items: T[];
  getDisplayName: (t: T) => string;
  getKey: (t: T) => string;
  renderItem: (t: T) => React.ReactNode;
}

class GenericComponent<T> extends React.Component<Props<T>, State> {
  render() {
    const { classes, items, getKey, getDisplayName, renderItem } = this.props;

    return (
      <div className={classes.root}>
        {items.map(item => (
          <div className={classes.item} key={getKey(item)}>
            <div>{getDisplayName(item)}</div>
            <div>{renderItem(item)}</div>
          </div>
        ))}
      </div>
    );
  }
}

//   👇 create a `type` helper to that output the external props _after_ wrapping it
type ExternalProps<T> = RemoveProps<Props<T>, 'classes'>;
export default withStyles(
  styles
)(GenericComponent) as <T extends any>(props: ExternalProps<T>) => any;
//                       👆 cast the wrapped component as a function that takes
//                          in a type parameter so we can use that type
//                          parameter in `ExternalProps<T>`

The main idea is to cast the wrapped component as a function that takes in a type parameter (e.g. T) and use that type parameter to derive the external props after the component has been wrapped.

If you do this, then you can specify a type parameter when using the wrapped version of GenericComponent e.g.:

<GenericComponent<string> {/*...*/} />

Hopefully the code is explanatory enough for those who still have this problem. In general though, I consider this relatively advanced typescript usage and it's probably easier to use any instead of a generic parameter in the props


Related Query

More Query from same tag