Cam Pedersen
Founder @ RubberDuck

TypeScript Generic React Components

January 26, 2021

Simplicity of composition is what makes React so powerful to work with. I recently discovered a pattern that enables more composable components. More specifically it allows us to delete a lot of anys. It seems obvious in retrospect but I haven't encountered it much.

Functional components are my favorite because the contract is super clear. If you pass these props, you get a ReactElement back:

type FormProps = {
  onSubmit: (values: any) => void;
}
const Form = ({ onSubmit }: FormProps): ReactElement => (

We usually think of types as compile-type information. Generics allow type information to be applied at runtime, passed like arguments. This is useful when you have functionality that depends on the data type.

Evolving the component

In our example above, we originally let values be any because Form won't know ahead of time what values may eventually be submitted. We can pass a type like an argument to fix this:

type FormProps<Values> = {
  onSubmit: (values: Values) => void;
};

We would then pass it to FormProps from the function definition:

const Form = <Values,>({ onSubmit }: FormProps<Values>): ReactElement => (

The trailing comma above is a special character to signal to JSX that <Values,> isn't the start of a Values component but rather a generic type signature.

Now when using form we can expect a pretyped submit handler!

<Form<{username: string}> onSubmit={({username}) => console.log(username)}>

I've also found this useful in generic Table components, but I'll leave that as an exercise to the reader.

A note on names

In official type definitions and most documentation you will find around generics, the prevailing pattern is to name generics a single letter.

I find this nuts. It's like reading math. Letters are cheap, and in my opinion using a descriptive type argument name is always worth it.

Remember, code is for humans, otherwise we'd be writing Assembly by hand.