59
votes

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

7
What happens if you replace the generic <T> with <T extends {}> ?Jonas Wilms
same error. As I want something like:const myCom: <T>SFC<Prop<T>> Naman Kheterpal
I added playground link in description @JonasW.Naman Kheterpal
@NamanKheterpal I encountered the same prob and investigated a bit of time for that. You can find my solution, how to declare and how to use a React SFC hereford04

7 Answers

43
votes

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>
    );
}
27
votes

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>
14
votes

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>();

UPDATE 08/03/2021

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>()
9
votes

I'm proposing a similar yet albeit slightly different solution (brainstormed with a friend). We were trying to create a Formik wrapper, and managed to get it working in the following fashion:

import React, { memo } from 'react';

export type FormDefaultProps<T> = {
  initialValues: T;
  onSubmit<T>(values: T, actions: FormikActions<T>): void;
  validationSchema?: object;
};

// We extract React.PropsWithChildren from React.FunctionComponent or React.FC
function component<T>(props: React.PropsWithChildren<FormDefaultProps<T>>) {
  // Do whatever you want with the props.
  return(<div>{props.children}</div>
}

// the casting here is key. You can use as typeof component to 
// create the typing automatically with the generic included..
export const FormDefault = memo(component) as typeof component;

And then, you use it like:

 <FormDefault<PlanningCreateValues>
        onSubmit={handleSubmit}
        initialValues={PlanningCreateDefaultValues}
      >
         {/*Or any other child content in here */}
        {pages[page]}
</FormDefault>

I haven't been able to achieve this with method expressions:

const a: React.FC<MyProp> = (prop) => (<>MyComponent</>);

2
votes

The Factory pattern presented here by @chris is great but I can't use React Hooks with it. So I'm using this one.

// Props
interface Props<T> {
  a: T;
}

// Component
export const MyComponent: <T>(p: PropsWithChildren<Props<T>>) => React.ReactElement = props => {
  return <div>Hello Typescript</div>;
};

If you don't need children you can remove PropsWithChildren part. Props decomposition and hooks work as well.

export const MyComponent: <T>(p: Props<T>) => React.ReactElement = ({ a }) => {
  const [myState, setMyState] = useState(false);
  return <div>Hello Typescript</div>;
};
0
votes

An example of generic stateless component according to jmattheis's post.

MyGenericStatelessComponent.tsx

import React from "react";

type Prop<T> = {
    example: T;
};

const MyGenericStatelessComponent: <T extends Record<string, number | string>>(props: Prop<T>) => JSX.Element = <
    T extends Record<string, unknown>
>(
    props: Prop<T>
): JSX.Element => {
    return (
        <div>
            Example Prop id: {props.example.id}, Example Prop name: {props.example.name}
        </div>
    );
};

export default MyGenericStatelessComponent;

Usage:

<MyGenericStatelessComponent example={{ id: 1, name: "test01" }} />
0
votes

Using T = any as @vadistic example works but you will not have any type checking. Use this code and you will have code completion and type checks.

interface IProps<TModel> extends RouteComponentProps {
    headerText?: string | React.ReactNode;
    collection: TModel[];
}

interface ICommonSortableType extends ISortableItem {
    id: number;
    isCorrectResponse: boolean;
}

interface ISortableItem {
    sortableId: number;
}    

type GenericFunctionalComponent<TModel> = React.FC<IProps<TModel>>;
const CommonSortableList: GenericFunctionalComponent<ICommonSortableType> = (props) => {
...
}

Can then be used like this:

class CommonSortableType {
    public sortableId: number = -1;
    public id: number = -1;
    public isCorrectResponse: boolean = false;
}

<CommonSortableList
    collection={item.commonSortableTypes} //Is CommonSortableType[]
    headerText={<FormattedMessage id="item.list" />}
</CommonSortableList>

class ExtendedOptionDto extends OptionDto implements ICommonSortableType {
    public sortableId: number = -1;
}

class OptionDto {
    public id: number = -1;
    public isCorrectResponse: boolean = false;
}

<CommonSortableList
    collection={item.extendedOptionDtos} //Is ExtendedOptionDto[]
    headerText={<FormattedMessage id="item.list" />}
</CommonSortableList>