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
![](/static/evolution-2bd48839c76ee4025c5df670dd649f94.png)
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.