I have a generic React component, say like this one:
class Foo<T> extends React.Component<FooProps<T>, FooState> {
constructor(props: FooProps<T>) {
super(props);
render() {
return <p> The result is {SomeGenericFunction<T>()}</p>;
}
}
I also have a HOC that looks similar to this one (but is less pointless):
export const withTd =
<T extends WithTdProps>(TableElement: React.ComponentType<T>): React.SFC<T> =>
(props: T) => <td><TableElement {...props}/></td>;
But when I use a component like this:
const FooWithTd = withTd(Foo);
There is no way to pass the type argument, as you can do neither withTd(Foo<T>)
, nor can you do FooWithTd
, the type is always wrong.
What is the proper way to do that?
EDIT: The problem is that I want to be able to have something like <FooWithTd<number> {...someprops}/>
later on, as I don't know the desired type for T
in the HOC.
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>>;
来源:https://stackoverflow.com/questions/51984093/typescript-with-react-use-hoc-on-a-generic-component-class