Generically-typed React components

I've been using React a lot on some recent projects, and enjoying it. The surrounding community and range of libraries on offer is (unsurprisingly) great, and if you're building a common component then it's often easy to find a well-documented open source version of what you're about to build.

You can see this code on GitHub, and it's also deployed to Netlify.

One tool I can no longer live without is [Flow][flow], a static type checker. It helps me spot errors with passing objects that might be incompatible, accessing variables that might not exist, and a whole host of other common pitfalls.

This weekend, I've been playing around with writing generically-typed components, that is: components where the type definition takes a parameter that is also a type. One of the most obvious examples is a list, so I'll use that as an example.

The types

By using a type parameter, <T>, we can define a type that takes an array of items, and a method that renders a single item. At the moment, think of `T` as a placeholder: we'll fill it in later.

#!javascript
// @flow
import * as React from "react";

// The list itself takes an array of items
// and a function to render each one
type Props<T> = {
  items: Array<T>,
  renderItem: T => React.Node
};

The component

Here, we're making a component that takes props of type Props<T>, and ensuring that the type is an object that defines a readable `key` property. It doesn't do anything too clever: it renders the items by calling `map` with our `renderItem` property:

#!javascript
export type ListItem = { +key: string };

export default class GenericList<T: ListItem> extends React.Component<
  Props<T>
> {
  renderListItem: T => React.Node = (item: T) => {
    return <li key={item.key}>{this.props.renderItem(item)}</li>;
  };

  render() {
    return <ul>{this.props.items.map(this.renderListItem)}</ul>;
  }
}

Using the component

Once we've defined our `GenericList` component, we can extend it, and define a class that fills in the `T` with a specific type. In this case, we'll make a list of `TodoItem` objects:

#!javascript
export type Task = ListItem & {
  title: string,
  isCompleted: boolean
};

type Props = {
  items: Array<Task>
};

export default class TaskList extends React.Component<Props> {
  renderItem = (task: Task) => {
    return (
      <span>
        {task.title} ({task.isCompleted ? "Done" : "Not done"})
      </span>
    );
  };

  render() {
    return (
      <GenericList items={this.props.items} renderItem={this.renderItem} />
    );
  }
}

And that's about it: our `GenericList` will only typecheck if the objects passed in have a `key` property, and our types will ensure that each item can always be rendered correctly.

Think of the `GenericList` like a contract: given a type `T`, and a function T => React.Node, we can render a list of any object with `key` property, and Flow will type check it all.

And that's it... I've written lists, tables, typeahead searches and more, and doing so has felt pleasant. A big thanks has to go to [William Chargin](https://github.com/wchargin) who helped me out when I [raised the question](https://github.com/facebook/flow/issues/7145) of how to do this. Thanks!