Best Practices for Flow Typing React Components

SITE NAME
2017-06-01T11:00:00+00:00

At Coursera, we use Flow to type our Javascript and catch mistakes at compile time. Flow has been enormously useful as a documentation tool and has increased the productivity of our frontend engineers. However, this tool can be tricky to use with React; it took us several months to figure out how we could truly make our types “flow” from one component to another while still catching errors. We want to share some of our best practices with you so that you can easily type React components, stateless functional components, and higher order components (HOCs).

Note that this blog post assumes passing familiarity with React and Flow. Flow works with React and JSX out of the box, but the types can sometimes be a bit tricky to deal with. For reference, you can take a look at the official Flow library definitions for React here.

Do I need to use React PropTypes if I use Flow?

If you use Babel, you should strip your Flow types from your outputted code using the babel-plugin-transform-flow-strip-types. Usually both React PropTypes and Flow types are stripped out in our production builds. PropTypes provide run-time type checking in the browser console, while Flow types provide compile-time type-checking.

If you are building a generic UI component that will be used in many places, it’s useful to add both PropTypes and Flow types so that developers see both compile-time errors and the red console PropType warnings when developing.

If you’re building an app that uses Flow types throughout, it’s usually not necessary to also include PropTypes.

If you are choosing between them, I recommend writing Flow types exclusively. They are usually more specific, easier to write, catch more errors, and can be statically analyzed for correctness without running the code.

Typing style

Here are some basic style guidelines for typing React components:

1. Prefer using React.Component<DefaultProps, Props, State> over props: Props; state: State; defaultProps: DefaultProps; in the function body. If DefaultProps or State are not used, use void in their place. We do this for consistency, clarity, and ease of reading the type definition for each React component.

2. Do not use * in place of DefaultProps. Using * can cause loss of type refinement because Flow expects props passed into a React component are $Diff<Props, DefaultProps>. This can be read as ‘at minimum, the types that are in Props that are not also in DefaultProps’. If * is used, Flow will attempt to infer a DefaultProps that fits the type definition, even if there are no DefaultProps, which results in silenced errors.

3. If you want to use React’s context, you’ll have to include contextTypes and also type context yourself, on the class body. Flow doesn’t track passing context and accepts any definition for it.

4. If you want to type a function that takes a React component, use the built-in type React$Component<DefaultProps, Props, State>. Ex: function takeComponentClass(component: Class<React$Component<*, *, *>>): void {};. The built-ino ReactClass type does not seem to type-check correctly.

5. To type refs correctly, you can use Element as shown below.

See this snippet on flow.org/try

Using higher order components (HOCs)

Higher order components are functions that take a React component and return a new React component that wraps the other component. We can use higher order components to change props that are passed down.

To write HOCs, we need to use two built-in special Flow types: - the React$Component<> type which represents a React component instance and is parameterized with DefaultProps, Props, and State - The Class<> built-in, which is parameterized with a single argument to get the class type of an instance

The input of an HOC is often a component class, typed as Class<React$Component<DefaultProps, Props, State>>, and the output is the same type, with the Props type transformed. This is best understood in an example:

See this snippet on flow.org/try

As noted in the above example, you cannot export components wrapped in HOCs directly and expect them to type-check in other files. This is because Flow is lazy and wants you to explicitly type exports. You must follow the pattern of typing the exported variable and then exporting that variable separately.

At Coursera, we use underscore’s compose function (_.compose) to type HOC chains. An example can be found here.

Typing functions that take in component classes

There are two ways of writing React components: stateless functional components and classes. when writing higher order functions, you’ll have to deal with both cases. Here’s how a function would take both a stateless functional component and a React component class:

See this snippet on flow.org/try

Writing a higher order component

Writing a higher order component is a bit more complex than using one. Here are some tips:

it’s usually easier to use .js.flow files instead of typing the HOC file. .js.flow files live next to a javascript file, and the flow file’s exports are used instead of the exports from the corresponding js file. The exports are really what matter with a higher-order component function.

HOCs should make sure to use void or {} for the DefaultProps type, because otherwise Flow may have problems inferring Prop types.

See this snippet on flow.org/try

Conclusion

As we’ve seen, using Flow and React together can be tricky for two reasons: Flow may fail to catch type errors when it should, and it may not catch type errors when they really exist. Hopefully these examples help you write modern React with the confidence that Flow offers.