/ React

Flow as Replacement for PropTypes

A couple of months ago at Product Hunt, we decided to switch from React.PropTypes to Flow. Initially, we started using Flow just for function definitions. Then we start replacing propType.

Why we do did that? Let's say we have an UserImage component with two possible uses:

<UserImage user={user} width={50} height={30} />
<UserImage user={user} variant="small" />

Its definition would be something like this:

const UserShape = {
  id: React.PropTypes.number.isRequired,
  name: React.PropTypes.name.isRequired,
};

class UserImage extends React.Component {
  static propTypes = {
    user: React.PropTypes.shape(UserShape).isRequired,
    width: React.PropTypes.number,
    height: React.PropTypes.number,
    variant: React.PropTypes.oneOf(['big', 'medium', 'small')
  };

  render() { /* ... */ }
}

There are some problems with that definition:

  • UserShape can be used only with other React components
  • propTypes throws a warning during runtime
  • width and height should be passed together, but we can't enforce that
  • It is possible to pass all three props - width, height, variant

Here is how the Flow solves those problems:

type User = {
  id: number,
  name: string,
};

class UserImage extends React.Component {
  props: {
    user: User,
    width: number,
    height: number,
  } | {
    user: User,
    variant: 'big' | 'medium' | 'small',
  };

  render() { /* ... */ }
}
  • User is a generic type (can be used for any javascript function)
  • Flow types are used only during build
  • <UserImage user={user} width="10" /> breaks the build ?

Unfortunately this is still possible:

<UserImage user={user} width={50} height={30} variant="small" />

One way to solve that is the following:

  props: {
    user: User,
    width: number,
    height: number,
    // expects "variant" is not passed
    variant?: void,
  } | {
    user: User,
    variant: 'big' | 'medium' | 'small',
    // expects "width" and "height" are not passed
    width?: void,
    height?: void,
  };

It's a bit ugly, but it gets the job done:

<UserImage user={user} width={50} height={30} variant="small" />
                       ^^^^^^^ number. This type is incompatible with void

<UserImage user={user} width={50} height={30} variant="small" />
                                   ^^^^^^^ number. This type is incompatible with void

<UserImage user={user} width={50} height={30} variant="small" />
                                                       ^^^^^^^ string. This type is incompatible with void

For more information check Flow documentation.

p.s. Gabriele Petronella and Vjeux told me on twitter about $Exact and {| |}:

  props: 
    {| user: User, width: number, height: number |} |
    {| user: User, variant: 'big' | 'medium' | 'small' |};