Flow
Published in

Flow

Even Better Support for React in Flow

The first version of Flow support for React was a magical implementation of . 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 class will take two type arguments, and (as opposed to the three type arguments including that took before). In the past Flow would infer and from your property definitions, but now Flow expects you to pass in and 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 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 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 component does not allow string children, the React Native component only allows for 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 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 prop returns a , 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:

  • , 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 and it is also the type that is returned from render methods and stateless functional components. A could be a string, number, boolean, null, React element, or an array of any of those values.
  • is is the type of any class component or stateless function component that expects props with the type . This type is incredibly useful as the input type for higher order components or any operation which needs to take a variable component type.
  • 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. allows you to model the data structure and cleanly interoperate with the API, which is typed in Flow 0.53.0.
  • 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 . For example: . However, this makes a lot less sense than passing in the component directly such as in: or . (The is important!) We’ve changed the 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. does this with the function. Here is how you would type a simple higher-order component that injects a number prop, :

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 utility along with an intersection to inject the prop .

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 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: .
  • 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 and .
  • Additions for the and utility types.
  • Add a type argument to s and correctly typed 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

is a utility to make upgrading when there are breaking changes in Flow much less painful. 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 with the feature:

yarn create flow-upgrade

…or if you could use :

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 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 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 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 Flow is actually invisibly adding type arguments turning into . 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 is rewritten to 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 is inconsistent. When inferring, in you would not add type arguments, but if you used as a type annotation like in: you must add type arguments. This means you need to remember the rules for whether or not 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!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Caleb Meredith

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