TypeScript Generic React Components
January 26, 2021Simplicity 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 any
s. 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.