Problem: The interface of Stateless Functional Component is given as
interface SFC<P = {}> {
(props: P & { children?: ReactNode }, context?: any): ReactElement<any> | null;
propTypes?: ValidationMap<P>;
}
The prop type of my component is also generic as:
interface Prop<V>{
num: V;
}
How to properly define my component? as:
const myCom: <T>SFC<Prop<T>> = <T>(props: Prop<T>)=> <div>test</div>
gives an error at character 27 that Cannot find name 'T'
Here is :Typescript Playground of modified example
MyFindings:
1:Typescript 2.9.1 support Stateful Generic Component: http://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#generic-type-arguments-in-jsx-elements
class myCom<T> extends React.Component<Prop<T>, any> {
render() {
return <div>test</div>;
}
}
2: Extending SFC to make a new interface as mentioned in following answer would make component's prop type as any:
Typescript React stateless function with generic parameter/return types which I don't want. I want to give proper type for my prop
You can't use generics like this:
const myCom: <T>SFC<Prop<T>> = <T>(props: Prop<T>)=> <div>test</div>
The TypeScript spec states:
A construct of the form
< T > ( ... ) => { ... }could be parsed as an arrow function expression with a type parameter or a type assertion applied to an arrow function with no type parameter.
source; Microsoft/TypeScript spec.md
Your declaration doesn't match the pattern defined in the TypeScript spec, therefore it wont work.
You can however don't use the SFC interface and just declare it yourself.
interface Prop<V> {
num: V;
}
// normal function
function Abc<T extends string | number>(props: Prop<T>): React.ReactElement<Prop<T>> {
return <div />;
}
// const lambda function
const Abc: <T extends string | number>(p: Prop<T>) => React.ReactElement<Prop<T>> = (props) => {
return <div />
};
export default function App() {
return (
<React.Fragment>
<Abc<number> num={1} />
<Abc<string> num="abc" />
<Abc<string> num={1} /> // string expected but was number
</React.Fragment>
);
}
There's a pattern to mitigate this issue by declaring generic component type alias outside of component and then simply asserting it when you need it.
Not as pretty, but still reusable and strict.
interface IMyComponentProps<T> {
name: string
type: T
}
// instead of inline with component assignment
type MyComponentI<T = any> = React.FC<IMyComponentProps<T>>
const MyComponent: MyComponentI = props => <p {...props}>Hello</p>
const TypedComponent = MyComponent as MyComponentI<number>
Factory pattern:
import React, { SFC } from 'react';
export interface GridProps<T = unknown> {
data: T[];
renderItem: (props: { item: T }) => React.ReactChild;
}
export const GridFactory = <T extends any>(): SFC<GridProps<T>> => () => {
return (
<div>
...
</div>
);
};
const Grid = GridFactory<string>();
To avoid rules of hooks errors you have to finesse the syntax like this:
import React, { FC } from 'react';
export interface GridProps<T = unknown> {
data: T[]
renderItem: (props: { item: T }) => React.ReactChild
}
export const GridFactory = <T extends any>() => {
const Instance: FC<GridProps<T>> = (props) => {
const [state, setState] = useState(props.data)
return <div>...</div>
}
return Instance
}
const Grid = GridFactory<string>()