Skip to content

Instantly share code, notes, and snippets.

@andrewsantarin
Last active May 12, 2023 20:13
Show Gist options
  • Save andrewsantarin/251cfe6f76efc3f596d02c9c7ee2444b to your computer and use it in GitHub Desktop.
Save andrewsantarin/251cfe6f76efc3f596d02c9c7ee2444b to your computer and use it in GitHub Desktop.
React generic function component prop types in TypeScript

React generic function component prop types in TypeScript

Usually, I write and use custom function components, in one way or other, in this general format:

import React from 'react';

export type MyInputProps = React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement & MyInputFieldProps<T>>, HTMLInputElement>

export const MyInput = React.FunctionComponent<> ({
  children,
  ...props
}: MyInputProps) {
  return (
    <>
      {children}
      <input {...props} />
    </>
  );
};

// Pay attention to how I use <MyInput> without generics. The rest of the details aren't important.
export const MyInputForm: React.FunctionComponent<{}> = (props) => {
  return (
    <form>
      <MyInput
        name="test"
        value="test"
        onChange={(event) => {
          const name = event.target.name;
        }}
      />
    </form>
  );
};

Sometimes, I find that the typings just aren't good enough. I want to convert the component type into something generic in order to leverage TypeScript typings and Visual Studio Code Intellisense, such that below will work:

<MyInput<'myFieldName', string>
  name="myFieldName" // It can't be any other string.
  value="myFieldValue" // Numbers will cause type errors.
  onChange={(event) => {
    const name = event.target.name; // string
    const value = event.target.value; // string
  }}
/>

So, what do you do when you need to inject the types? Use generics.

But then, I run into this:

// TypeScript won't accept this. Neither does Intellisense.
export const MyInputForm: React.FunctionComponent<MyInputProps<Generics>> = <Generics>(props) => {
  // ...the implementation
};

However (and quite unfortunately), making generics using React.FunctionComponent<MyInputProps<T>> won't work.

If you really want generics on your function component, ditch React.FunctionComponent<YourComponentProps> and fat arrow functions (() => {}). Switch to a format that works. A bird's eye view:

export const YourComponent = function <T>(props: YourComponentProps<T>) {
  return (
    // ...your JSX here.
  );
};

It's no React.FunctionComponent (it's actually more like () => JSX.Element), but it'll do!

See MyInput.tsx for an example.

import React from 'react';
export type MyInputFieldProps<
NameType extends string = string,
ValueType extends string | string[] | number | undefined = string | string[] | number | undefined
> = Partial<{
name: NameType;
value: ValueType;
}>;
export type MyInputProps<
NameType extends string = string,
ValueType extends string | string[] | number | undefined = string | string[] | number | undefined
> =
& MyInputFieldProps<NameType, ValueType>
& React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement & MyInputFieldProps<NameType, ValueType>>, HTMLInputElement>
& {
children?: React.ReactNode;
};
export const MyInput = function MyInput<
NameType extends string = string,
ValueType extends string | string[] | number | undefined = string | string[] | number | undefined
>({
children,
...props
}: MyInputProps<NameType, ValueType>) {
return (
<>
{children}
<input {...props} />
</>
);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment