How to create stateless functional components that accept generics?
Generic type variables can be used in function and type declarations like your interfaces Sample1
and Sample2
.
But when you actually use/call/invoke generic types, you have to specify a concrete type argument instead of P
.
React.SFC
- the stateless function component type - is an interface declaration, so using it in an const
assignment requires a concrete type for P
.
The only solution that comes to my mind is to make the SFC
look like a function
type for the ts-compiler, since function declarations/expressions can
be assigned generic type parameters. See Typescript docs for some examples.
Generic SFC declaration
Leave out the React.SFC
type annotation to your assignment const SampleSFC
:
const SampleSFC = <P extends {}>(props: Sample2<P>) => <div>{props.prop1} </div>;
Note: The
<P extends {}>
part is needed due to JSX/TSX compatibility issues with generics, see the end of this post.
This way, your component remains a plain function. If you need the additional members from React.SFC
, add them to your props. E.g. if children
needed:
const SampleSFC = <P extends {}>(props: Sample2<P> & { children?: ReactNode })
=> <div >{props.prop1} </div>;
Generic SFC usage
Since Typescript 2.9, the type parameter can be directly set in JSX.
const MyApp: React.SFC<{}> = (props) => {
return <SampleSFC<number> prop2="myString" prop1={1} />
}
Alternatives
1.) A switch to class components would be easy, if you don't like the quirks.
class SampleSFCClass<P> extends React.Component<Sample2<P>> {
render() {
return <div>{this.props.prop1}</div>
}
}
const MyApp2: React.SFC<{}> = (props) => {
return <SampleSFCClass<number> prop2="myString" prop1={1} />
}
2.) You could even wrap the SFC
in another function.
const withGeneric: <P>() => React.SFC<Sample2<P>> = <P extends {}>() => {
return (props) => <div> {props.prop1} {props.prop2} </div>;
}
const SampleSFCCalled: React.SFC<Sample2<string>> = withGeneric<string>();
const MyApp: React.SFC<{}> = (props) => {
return <SampleSFCCalled prop2="myString" prop1="aString" />
}
It will work, but disadvantage might be slight performance decrease, because the SFC
funtion is always recreated in each render cycle of the parent comp.
JSX/TSX compatibility issues with Generics:
In some constellations Typescript seems to have problems parsing generics in combination with JSX/TSX (up to most recent 3.0.1) due to ambiguous syntax. Compile error will then be:
"JSX element has no corresponding closing tag."
One of the contributors recommended to use the function
syntax in this case (see issue).
When you stick to arrow function, workaround is to let the type parameter extend from object (shown here or here) to clarify its meant to be a generic lambda and not a JSX tag:
<P extends {}>
or <P extends object>
Hope, that helps.