Reputation: 11770
How can i write a generic that takes in a component and use its prop type as the type of the second parameter
Lets say i pass in a component with the type of React.FunctionComponent<IMovieShowcase>
How can i extract the type of IMovieShowcase
function renderWithProviders<T How to extract type of T<Props>>(
Component: T, props: // props should be IMovieShowcase
) {
return (
<MemoryRouter>
<MuiThemeProvider theme={createMuiTheme()}>
<Component {...props} />
</MuiThemeProvider>
</MemoryRouter>
);
}
Upvotes: 1
Views: 1130
Reputation: 5650
We can use TypeScript's type inference in conditional types.
This InferProps
accepts the generic named Component
. So it can be used as InferProps<YourComponent>
to return the type of the props. As far as I am aware of there is the ComponentClass
and FunctionComponent
types that are valid React components. Since we have to handle both we can use a nested conditional (hence the double ?
).
The first conditional statement Component extends React.ComponentClass<infer Props>
is saying if our Component
extends a React.ComponentClass
then infer
the Props
and return that type. If it's not, then check Component extends React.FunctionComponent<infer Props>
. If Component
extends a React.FunctionComponent
then infer
the Props
and return that type. Otherwise, return never
because we're not sure how to handle or what it is so we can't infer the props.
type InferProps<
Component extends ComponentTypes
> = Component extends React.ComponentClass<infer Props>
? Props
: Component extends React.FunctionComponent<infer Props>
? Props
: never;
Used in a simplified example with the code you provided:
import * as React from "react";
type ComponentTypes = React.ComponentClass<any> | React.FunctionComponent<any>;
type InferProps<
Component extends ComponentTypes
> = Component extends React.ComponentClass<infer Props>
? Props
: Component extends React.FunctionComponent<infer Props>
? Props
: never;
function renderWithProviders<T extends ComponentTypes>(
Component: T,
props: InferProps<T>
) {
return <Component {...props} />;
}
class Test extends React.Component<{ foo: string }> {
render() {
return null;
}
}
const Another = (props: { baz: number }) => null;
// Valid:
renderWithProviders(Test, { foo: "bar" });
// Valid:
renderWithProviders(Another, { baz: 1 });
// Invalid:
// Object literal may only specify known properties,
// and 'baz' does not exist in type '{ foo: string; }'
renderWithProviders(Test, { foo: "bar", baz: "foo" });
// Invalid:
// The expected type comes from property 'baz' which is
// declared here on type '{ baz: number; }'
renderWithProviders(Another, { baz: "nope" });
Upvotes: 3