Typescript with React - use HOC on a generic component class

眉间皱痕 提交于 2019-11-30 14:17:11

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.

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

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

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>>;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!