Type of Generic Stateless Component React? OR Extending generic function interface in typescript to have a further generic?

JavascriptReactjsTypescriptGenerics

Javascript Problem Overview


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: https://stackoverflow.com/questions/44392479/typescript-react-stateless-function-with-generic-parameter-return-types# which I don't want. I want to give proper type for my prop

Javascript Solutions


Solution 1 - Javascript

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

Solution 2 - Javascript

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>

Solution 3 - Javascript

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

Solution 4 - Javascript

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

Solution 5 - Javascript

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>;
};

Solution 6 - Javascript

i have a way to do, but I'm not sure is perfect or not

interface ComponentProps<T> {
    text: T;
}

export const Component= <T,>(props: ComponentProps<T>) => {
  const { text } = props
  const [s] = useState(0) // you can use hook
  return (<div>yes</div>)
}

you can use component like this:

 (
  <>
    <Component<string> text="some text" />
  </>
 )

Solution 7 - Javascript

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" }} />

Solution 8 - Javascript

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>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNaman KheterpalView Question on Stackoverflow
Solution 1 - JavascriptjmattheisView Answer on Stackoverflow
Solution 2 - JavascriptvadisticView Answer on Stackoverflow
Solution 3 - JavascriptchrisView Answer on Stackoverflow
Solution 4 - JavascriptJose AView Answer on Stackoverflow
Solution 5 - JavascriptPavel KrčmářView Answer on Stackoverflow
Solution 6 - JavascriptAndy HoView Answer on Stackoverflow
Solution 7 - JavascriptserdarsenView Answer on Stackoverflow
Solution 8 - JavascriptOgglasView Answer on Stackoverflow