28

I'm trying to make a React component which takes in a generic type parameter which will be part of its prop type.

I want a solution that would look something like this:

interface TestProps<T> {
  value: T;
  onChange: (newValue: T) => void;
}

const Test: React.FC<TestProps<T>> = (props) => (
  <span>{props.value}</span>
);

I have seen that there is support for this in TypeScript 2.9 and I'm on 4.3.5.

Usage of this would look like this:

const Usage: React.FC = () => (
  <div>
    <Test<Obj>
      value={{ name: 'test' }}
      onChange={(newValue) => {
        console.log(newValue.name);
      }}
    />
  </div>
);

Code sandbox: https://codesandbox.io/s/react-typescript-playground-forked-8hu13?file=/src/index.tsx

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Jeggy
  • 1,474
  • 1
  • 19
  • 35
  • 2
    Just without `React.FC` helper type: `function Test(props: TestProps) { ...` https://codesandbox.io/embed/react-typescript-playground-forked-lgnid?fontsize=14&hidenavigation=1&theme=dark – Aleksey L. Aug 12 '21 at 12:25

3 Answers3

28

You need to rewrite your Test component in this way

const Test= <T,>(props:TestProps<T>) => (
    <span>Some component logic</span>
);

Can you show the same with React.FC<TestProps>? It is impossible to do with FC.

This is FC implementation:

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
  // ... other static properties
}

As you might have noticed, FC is a function type, not type of props.

UPDATE

You can create higher order function, but I'm not sure if it worth it

const WithGeneric = <T,>(): React.FC<TestProps<T>> =>
  (props: TestProps<T>) => (
    <span>Some component logic</span>
  );
const Test = WithGeneric<Obj>()
22

The easiest way is to make the generic FC a regular function, not an arrow function. (React.PropsWithChildren<> emulates what React.FC does to your props type.)

function Test<T>(props: React.PropsWithChildren<TestProps<T>>) {
    return <span>Some component logic</span>;
}
AKX
  • 152,115
  • 15
  • 115
  • 172
  • I've been agonizing over this recently. This is fine and dandy however there's no explicit statement in the code that Test is in fact a `FC>` for some arbitrary type `T`. AAMOF my understanding is that this kind of statement cannot be expressed but I don't have a clear mental model as to where the limitation originates from. Is it a shortcoming of the TS type system, or of the particular way `FC` is defined in React? – Marcus Junius Brutus Oct 13 '22 at 12:07
  • you've missed something, `React.FC` does not only make sure the props are using type checking. but it also makes sure the return value of your function (function component) is valid, your function in the code above for example doesn't make sure of that – Normal Mar 10 '23 at 22:05
14

In my case it was like the following codes:

export interface FormProps<T> {
  validator?: AnyObjectSchema;
  onSubmit?: (data: T) => void;
}

const Form = <T = any,>({
  children,
  validator,
}: PropsWithChildren<FormProps<T>>): JSX.Element => {
  ~~~

And in usage:

type MyType = ...

<Form<MyType>
  validation={something}
  onSubmit={handleSomething}
>
  <SomeCompo />
  <AnotherSomeCompo />
</Form>
AmerllicA
  • 29,059
  • 15
  • 130
  • 154