How to make a functional React component with generic type?
Asked Answered
A

3

35

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

Antipater answered 12/8, 2021 at 12:19 Comment(1)
Just without React.FC helper type: function Test<T>(props: TestProps<T>) { ... codesandbox.io/embed/…Chime
I
32

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>;
}
Influenza answered 12/8, 2021 at 12:25 Comment(2)
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<TestProps<T>> 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?Abshier
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 thatRumen
T
31

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>()
Tevet answered 12/8, 2021 at 12:26 Comment(7)
Can you show the same with React.FC<TestProps<T>>?Influenza
Made an update. It is impossible with FCTevet
Yes, FC is a function type, but the point of my question was that can you show a syntax that would allow annotating the generic arrow function with React.FC so it'd apply the same types (modified props, annotated return type, possible static additions).Influenza
I made an update. you can wrap your component in higher order function. It worth use value argument to infer T instead of explicit generic parameter without value but it is up to youTevet
react native's Fast Refresh did not work for that component. When the parent component is saved, it reloaded. Does anyone know why it doesn't work?Tamper
I think the HOL hack tortuous though it is, is the only way in TypeScript / React to explicitly assert in the code that a certain value is a React.FC for some generic Props<T>.Abshier
The trick is the ,: <T,>() => <div />.Grant
C
15

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>
Carinthia answered 9/6, 2022 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.