Using Flow Generics at Coursera
--
In popular programming languages like Java and C#, generics serve as one of the tools for creating reusable components. By using generics, the programmer is able to create safe classes and functions that can operate on objects of various types.
Similarly, Flow offers generics as a way of type abstraction. Check out the docs on the official website to learn the basics. In this article, I want to give you an example of how we use Flow Generics with React components at Coursera.
ItemSelector component
Imagine you want to build a course selector component, consisting of checkboxes and course labels. A user can select courses in any combination, up on submission an API call is triggered to send the selected items back to the server.
CourseSelector takes in an array of course objects as a prop:
/* CourseSelector.js */// @flow
...type Course = {
title: string, // used as checkbox label
partners: Array<Partner>,
lecturers: Array<Lecturer>,
};type Props = {
courses: Array<Course>,
onSelect: (Array<Course>) => void,
...
};class CourseSelector extends React.Component<Props> {
...
render() {
// Map through items object and display a checkbox and item.title for each item.
}
}export default CourseSelector;
Now, let’s say another project comes up, and this time we are asked to create a video selector interface. Since the functionality and UI of our new video selector component is the same as that of CourseSelector, it makes sense for us to rewrite CourseSelector into ItemSelector, turning it into a reusable component that can work with any other input object. We also want ItemSelector’s flow types not to be bounded to any particular item shape. A perfect use case for generics!
/* ItemSelector.js */// @flow...type Props<T> = {
items: Array<T>,
onSelect: (Array<T>) => void,
...
};class ItemSelector<T> extends React.Component<Props<T>> {
...
render() {
// Map through items object and display a checkbox and item.title for each item.
}
}export default ItemSelector;
By adding type argument T
to our ItemSelector component and propagating it along to Props, we guarantee that the onSelect
property of our component must be a function that is operable on items
.
Adding the generic type T
is a good first step. However, we don’t yet have a guarantee that the user will pass in a type that would work with our selector (remember that this component maps through items
to display item.title
for each item
). Since the functionality of our component is dependent on T
being an object with a required title
field, we can incorporate this constraint by adding a :
and putting it after the generic type declaration.
/* ItemSelector.js */...class ItemSelector<T: {title: string}> extends React.Component<Props<T>> {
...
}
With this syntax, we have enforced the input type T
to be a type that our ItemSelector can operate on: an object with a mandatory field title
.
Going back to our video selector functionality, we can simply pass in Video
type into ItemSelector
component to let Flow know that our ItemSelector
class is operable on this type. Note that ItemSelector needs to be wrapped in Class
, or otherwise Flow would interpret VideoSelector
as an instance of ItemSelector
.
/* ParentComponent.js */// @flowimport ItemSelector from './ItemSelector ';type Video = {
title: string,
author: ?string,
length: ?number,
};const VideoSelector: Class<ItemSelector<Video>> = ItemSelector;
...
And there you have it! With this, you can code confidently without having to worry about the type safety of your VideoSelector component. Flow will output a warning if an invalid prop is passed in.
/* ParentComponent.js */
...class ParentComponent extends React.Component<Props> {
... onVideoSelect(selectedVideos: Array<Video>) {
console.log('Number of videos selected:', selectedVideos.length)
} render() {
const incorrectItemsType = [
{
foo: 'bar',
},
{
baz: 'bar',
}
];
// Valid onSelect prop but invalid items prop return (
<VideoSelector
items={incorrectItemsType} // Error!
onSelect={onVideoSelect}
/>
)
}
}export default ParentComponent;
Here is an interactive REPL that you can play with to see the errors Flow would output when an invalid type is passed in as props.
Conclusion
Flow is powerful in helping you write and maintain type safe code when used properly. Front-end devs at Coursera use Flow extensively. Before Flow came along, we relied heavily on React.PropTypes to do basic this.props
type checking for us. The adoption of Flow has enabled us to do type analysis in a more comprehensive way, all the while without going through the “save and reload in the browser” cycle, thus boosting the developer productivity. Generics is just one of the many advanced functionalities that the library offers. Thank you for reading. I hope you found this article useful.