Even Better Support for React in Flow

Caleb Meredith
Flow
Published in
8 min readAug 16, 2017

--

The first version of Flow support for React was a magical implementation of React.createClass(). Since then, React has evolved significantly. It is time to rethink how Flow models React.

In Flow 0.53.0 we are changing how Flow models React and in doing this we can provide better types for higher-order components, strict typing for React children, and fixes for a host of bugs in Flow’s React support.

The biggest change we are making is to modify how you define React class components. From version 0.53.0 and on, the React.Component class will take two type arguments, Props and State (as opposed to the three type arguments including DefaultProps that React.Component took before). In the past Flow would infer Props and State from your property definitions, but now Flow expects you to pass in Props and State explicitly. A React component in 0.53.0 looks like this:

type Props = {
foo: number,
};
type State = {
bar: number,
};
class MyComponent extends React.Component<Props, State> {
state = {
bar: 42,
};

render() {
return this.props.foo + this.state.bar;
}
}

When your component has no state, you only need to pass in a single type argument. The second type argument that represents state will default to undefined. Without state your component definition would look like this:

type Props = {
foo: number,
};
class MyComponent extends React.Component<Props> {
render() {
return this.props.foo;
}
}

If your component has default props then add a static defaultProps property:

type Props = {
foo: number,
};
class MyComponent extends React.Component<Props> {
static defaultProps = {
foo: 42,
};
render() {
return this.props.foo;
}
}

Flow will infer the type of default props from your static property. You don’t need to provide Flow with any type annotations for default props. The same thing will work for functional components:

function MyComponent({ foo }: { foo: number }) {
return foo;
}
MyComponent.defaultProps = {
foo: 42,
};

You will notice that the components in our examples have been returning numbers from their render methods. This is because Flow’s React types have been updated for React 16. Flow will now let you return strings, arrays, and of course numbers from render methods. Once React 16 is released, React will render those values.

Later in this article I’ll discuss why we are no longer inferring the types of props and state. In short, it was confusing to teach and could be bad for performance.

We will also be providing simple-to-run codemods with the flow-upgrade utility, which I’ll also discuss later in the article.

For now let’s move on and talk about what else we are including in Flow 0.53.0 for React.

Strict Typing for React Children

As the React community has evolved, we have seen some incredibly creative use cases for React children. For example, the React Native <View> component does not allow string children, the React Native <TabBarIOS> component only allows for <TabBarIOS.Item> children, and React Router 4 includes an API that wants a function for children which looks like this:

<Route path={to}>
{({ match }) => (
<li className={match ? 'active' : ''}>
<Link to={to} {...rest} />
</li>
)}
</Route>

Now Flow will be able to type these children based APIs to ensure safety in your React code. For the React Router example above, you would type the <Route> component’s children as:

type Props = {
children: (data: { match: boolean }) => React.Node,
path: string,
};
class Route extends React.Component<Props> { /* ... */ }

To learn more about typing React children read our documentation guide on the topic.

The children prop returns a React.Node, which is the type for any value that can be rendered by React. This also brings us to the next feature we are adding to Flow’s React support…

New Utility Types to Model Advanced Patterns

Modeling advanced React patterns, like higher-order components, is difficult today because the types you would need are either not provided or undocumented. In this release we added a whole suite of utility types which are all documented on our website. Some of the highlights include:

  • React.Node, which we were introduced to earlier, represents any value which can be rendered by React. It is the type of the children for JSX intrinsic elements like <div> and it is also the type that is returned from React.Component render methods and stateless functional components. A React.Node could be a string, number, boolean, null, React element, or an array of any of those values.
  • React.ComponentType<Props> is is the type of any class component or stateless function component that expects props with the type Props. This type is incredibly useful as the input type for higher order components or any operation which needs to take a variable component type.
  • React.ChildrenArray<T> is the type for the React children data structure. React children are not a simple array: they could be a single value or an arbitrarily nested array. React.ChildrenArray<T> allows you to model the data structure and cleanly interoperate with the React.Children API, which is typed in Flow 0.53.0.
  • React.Element<Type> is not a new type, but it has a new API! In the past you would pass the type of the element’s props to React.Element. For example: React.Element<MyComponentProps>. However, this makes a lot less sense than passing in the component directly such as in: React.Element<’div’> or React.Element<typeof MyComponent>. (The typeof is important!) We’ve changed the React.Element<Type> API so that it takes the type of your component instead of the type of your component’s props.

These are some of the highlights, but there are many more utility types that you can read about in our documentation.

Typing Higher-order Components

Now that we have these utility types, it is much easier to type a higher-order component. We have a full documentation guide on our website that will teach you how to type higher-order components, but let’s run through a quick example here. Often a higher-order component will “inject” a prop stored in context. react-redux does this with the connect() function. Here is how you would type a simple higher-order component that injects a number prop, foo:

function injectProp<Props: {}>(
Component: React.ComponentType<{ foo: number } & Props>,
): React.ComponentType<Props> {
return function WrapperComponent(props: Props) {
return <Component {...props} foo={42} />;
};
}
class MyComponent extends React.Component<{
a: number,
b: number,
foo: number,
}> {}
const MyEnhancedComponent = injectProp(MyComponent);// We don't need to pass in `foo` even though
// `MyComponent` requires it!
<MyEnhancedComponent a={1} b={2} />;

Here we use the new React.ComponentType<Props> utility along with an intersection to inject the prop foo.

Even More

There is a lot more that we fit into this release for our React support. If we missed anything in improving our React support let us know.

  • Correct types for the class component setState() updater function.
  • Correct types for React ref functions.
  • Allow JSX class components to use exact object types as their props.
  • Fix member expressions when creating JSX components. Like in: <TabBarIOS.Item>.
  • Fix for multiple spreads in a JSX element.
  • Fix that sometimes allowed undefined values for a prop even if the prop was required.
  • Improved types for React.cloneElement() and React.createFactory().
  • Additions for the React.ElementProps<Type> and React.ElementRef<Type> utility types.
  • Add a type argument to SyntheticEvents and correctly typed currentTarget using that type argument.
  • Conversion for the React type definitions from a CommonJS to an ES module.

Because we fixed a lot of bugs, Flow will likely catch a lot more errors than it did before in your React code. If you see a lot of errors it’s because Flow got a lot better at checking React!

As you may have noticed we also made a couple of breaking changes which is also why we are announcing…

flow-upgrade

flow-upgrade is a utility to make upgrading when there are breaking changes in Flow much less painful. flow-upgrade is the exact same tool we use to upgrade our code internally at Facebook and it is available as an npm module that anyone can use.

You can run flow-upgrade with the yarn create feature:

yarn create flow-upgrade

…or if you could use npx:

npx flow-upgrade

If you have any trouble upgrading, let us know by sending us a message on Twitter: flowtype.

Why are props and state not inferred anymore?

This section is for people who are curious about why we are moving away from the style where we inferred the type of props in React class components instead of explicitly listing it as a type argument.

The new style for defining React components is:

type Props = {
foo: number,
};
class MyComponent extends React.Component<Props> {
render() {
return this.props.foo;
}
}

…but some people may prefer the old style in which the Props type argument was inferred:

type Props = {
foo: number,
};
class MyComponent extends React.Component {
props: Props;
render() {
return this.props.foo;
}
}

There are some good reasons behind this change. Firstly we believe that the new style is more aesthetically pleasing. Often you will create a props type above your component and so writing props: Props is redundant. However, we realize not everyone will agree with this aesthetic preference. There are also a number of technical reasons why we are making this change:

  1. The inference style is less clear to teach and to understand. While the style may look intuitive even though you haven’t defined type arguments for React.Component Flow is actually invisibly adding type arguments turning React.Component into React.Component<*, *, *>. Teaching how this happens is difficult and it also requires an understanding of how Flow internals work which is needed to understand how * works.
  2. The inference style may hurt performance. Because extends React.Component is rewritten to extends React.Component<*, *, *> we are creating three new type variables (one for each *) and are carrying their state around as we type check. By placing a concrete type definition there instead we won’t have to create these type variables and carry around their baggage. There are also some cases where you may inadvertently add extra constraints to your props type which causes bugs.
  3. When we recommend the inference style the parameterization of React.Component is inconsistent. When inferring, in extends React.Component you would not add type arguments, but if you used React.Component as a type annotation like in: const fn = (instance: React.Component<DefaultProps, Props, State>) => { /* … */ }; you must add type arguments. This means you need to remember the rules for whether or not React.Component needs to be parameterized.

We want to provide a delightful type checking experience for you as you write JavaScript and we hope that this update will help improve your experience writing React code with Flow.

Are you creating something cool with React and Flow? We want to hear about it. Send a message to flowtype on Twitter so we can share what you are building with our community so that we all may celebrate your efforts to make writing JavaScript more delightful!

--

--

Caleb Meredith
Flow

Product engineer at Airtable. Previously Facebook. @calebmer on Twitter