Using Flow generics to type generic React components
October 16, 2018 / 4 min read
Last Updated: October 16, 2018This post is part of an informal series of small articles focusing on Flow types usages that I’ve discovered and found useful over the past few months. The first article of this series can be found here.
Building generic React components can be a great way to simplify the codebase of a project. By combining overlapping functionalities into a single component, we reduce the entropy of components and thus reduce the amount of testing needed. However, typing generic React components can be a bit of hassle at first, especially when you‘re just getting started with Flow. This post will focus on one example of a generic React component that I was struggling to type a few months ago, and how Flow generics helped me to not only overcome the typing issues but also to build a scalable type for this component.
From multiple typed components to one
In the project I was working on, I had to handle multiple resources (named here Resource1
, Resource2
, Resource3
, … for simplicity). Each resource had its own associatedList
component (Resource1List
, Resource2List
, …), each of which was pretty similar to the others in terms of implementation, as the resources themselves were quite the same outside of a few differences. Below, you can see the types of Resource1
and Resource2
:
Example resource Flow types
1type Resource1 = {2id: string,3name: string,4};56type Resource2 = {7Id: string,8name: string,9};
So given these specs, I wanted to build a single ResourceList
component to display items of a given resource. Its props would include an onItemClick
callback function, which takes a variable of that resource type as an argument. Here’s the first type that I wrote for the props of the ResourceList
component:
The first iteration of the “Props” Flow type for ResourceList
1type Props = {2// other props3onItemClick = (4Resource1 |5Resource2 |6// other resource Flow types fo here7) => void,8}
Now that the component is typed, the next step consists of trying to use it with one of our Resources. Here’s how ResourceList
can be used for Resource1
for example:
Resource1List React component using ResourceList
1import React from 'react';2import ResourceList from 'components/ResourceList';3import type { Resource1 } from 'core/types';4...56class Resource1List extends React.Component {7onResource1Click = (item: Resource1) => {8const { name, id } = item;9// some action on Resource1 fields10};1112...1314render() {15return <ResourceList onItemClick={this.onResource1Click(item)} />;16}17}
This is the moment when I ran into the main problem. Given the way I typed this component, running Flow against the code in the example above outputs an error:
In the Resource types we declared above ( Resource1
, Resource2
, …), you can see that the key of the “id” field of each Resource doesn’t match. Thus, whenever we will use this component and write an onItemClick
function for a resource, Flow will give you an error telling us the properties of the other resources are missing.
How to use generics
To overcome this kind of situation, generics can be very practical. This is how we can end up typing this component and fix this issue:
- we can provide a generic
BaseProps
type which takes a generic typeT
- use
T
insideBaseProps
for ouronItemClick
function which will take an item of typeT
as argument and thus be of typeT => void
- declare
Resource1Props
,Resource2Props
, etc, based onBaseProps
- write the type
Props
as an enum ofResource1Props
,Resource2Props
, etc.
The resulting code looks like this:
ResourceList “Props” Flow type using a generic BaseProps Flow type
1type BaseProps<T> = {2// other props3onItemSelect = T => void,4};56type Resource1Props = BaseProps<Resource1>;7type Resource2Props = BaseProps<Resource2>;89type Props = Resource1Props | Resource2Props;
Running Flow with this type should output the following:
We have now typed our generic list component properly thanks to Flow. We can see that using generics not only brings flexibility to our type, but also will help in the future when we want to scale up the usage of this component, so it can be used with even more resources.
Liked this article? Share it with a friend on Bluesky or Twitter or support me to take on more ambitious projects to write about. Have a question, feedback or simply wish to contact me privately? Shoot me a DM and I'll do my best to get back to you.
Have a wonderful day.
– Maxime
How Flow generics help typing complex multi-purpose components