New Flow Language Features for React

Write Safer and more Succinct React with Flow

Alex Taylor
Flow

--

We have thousands of engineers committing React code every day to Meta’s largest codebases. Part of our responsibility on the Flow team is to make it as easy as possible for anyone to contribute, from design system React experts to C++ engineers making one-off internal pages to support their backend services. Over the last year, we’ve built several new language features to make it easier than ever to write high-quality React code, and we’ve built powerful new tools for design system owners to express their design constraints in the type system itself.

Flow is excited to announce Component Syntax, adding first-class support for React primitives such as components and hooks to the Flow language. These features bring improved ergonomics, expressiveness, and static enforcement for many of the Rules of React.

We’ve already adopted Component Syntax across all our codebases at Meta, and the results have been incredible: we’ve seen a massive reduction in boilerplate for writing components, we’ve caught thousands of violations of the Rules of React, and we’ve seen design systems codify their stylistic rules in the type system. Most importantly, our engineers love the new features. We’re excited to share these features with the broader community.

If you’re already using React in your own Flow projects, you can enable these new features by upgrading to Flow v0.233.0 or later.

None of this is required to use React, but if you’re already using Flow these features may be interesting to you.

Component Syntax Features

Component Syntax introduces several new features:

  • New Component declaration. Dedicated syntax for defining components that is ergonomic to use, reduces boilerplate code, and provides greater safety by enforcing many of the Rules of React.
  • New Hook declaration. Dedicated syntax for defining hooks that provides additional safety by enforcing the rules of hooks.
  • Statically enforced Design System rules with render types. We’ve introduced render types, a powerful tool for design systems that makes it simple to express stylistic constraints through types.

A Tour of Component Syntax

Code today 😴

type Props = $ReadOnly<{
text?: string,
onClick: () => void,
}>;

export default function HelloWorld({
text = 'Hello!',
onClick,
}: Props): React.MixedElement {
return <div onClick={onClick}>{text}</div>;
}

Component Syntax 🔥

export default component HelloWorld(
text: string = 'Hello!',
onClick: () => void,
) {
return <div onClick={onClick}>{text}</div>;
}

The Basics

As you can see, components are quite similar to functions, but with a few differences:

  • Replace the function keyword with component.
  • Use individual params instead of a props object, specifying default values inline is supported.
    – This removes the duplication required when using object destructuring and removes the need for modifiers like $ReadOnly<{…}>.
  • No return type is needed unless using render types. Flow enforces the returned value will always be a subtype of React.Node.

Full compatibility

Components are designed to fully replace their functional counterparts. To achieve this, we’ve added a few more features to support all prop types, include rest parameters, and props that use invalid JavaScript identifiers, as with html data attributes (data-<attr>).

export default component HelloWorld(
'data-testid' as testid: string, // [1]
...otherProps: OtherProps // [2]
) {
return <div data-testid={testid} {...otherProps} /></div>;
}
  1. String props are allowed but must be renamed via as.
  2. Creating props objects via rest params is also supported.

Improved Safety

Beyond the obvious syntactic advantages, Component Syntax also introduces new capabilities that allow Flow to better understand your code and enforce React best practices to help avoid common pitfalls. In the next sections we’ll cover a few of these features.

Ensuring props are deep read-only

One of the basic rules of React is that a component’s props shouldn’t be mutated. Historically, this behavior has been partially enforced by the common practice of wrapping prop types with $ReadOnly<>. With Component Syntax, all props are checked to ensure they are not mutated during render. This means that you won’t need to add $ReadOnly<> anymore (including on rest props), and you’ll get additional Flow coverage to ensure that properties are deeply protected from writes as well.

type Item = { 
itemName: string,
arr: Array<number>,
};
type OtherProps = {
userId: number,
};

component UserItem(userItem: Item, ...props: OtherProps) {
props.userId = 0; // Error: react-rule-unsafe-mutation
userItem.itemName = "Cool New Laptop"; // Error: react-rule-unsafe-mutation

const copiedObj = {...userItem};
copiedObj.arr[0] = 3; // Error: Deep checking catches error through copied object
}

The last error above was particularly exciting for us to catch! We’ve seen many cases where the programmer clearly understood the Rules of React and would copy objects instead of mutating them directly, but they would still erroneously mutate values that were only aliased, not copied. Immutability can be subtle even for experienced developers.

This feature helps prevent a common class of bugs while removing the need for boilerplate that was previously universal. This feature does have its limits, though. At present Flow is only able to detect mutations of props in a component or hook, and we optimistically assume that method calls do not cause mutations.

Enforcing best practices with Refs

Another pattern we enforce in component bodies is that refs are not read or written to during render. This is another common pitfall that can lead to unintended behavior. Check out the React documentation for more information. Here is an example of this in action:

component MyComponent() {
const renderCount = useRef<number>(0);
renderCount.current += 1; // error
return <div>{renderCount.current}</div> // error
}

In order to safely use refs in your components, you can read or write to refs from event handlers or effects instead:

component MyComponent() {
const renderCount = useRef<number>(0);
const [count, setCount] = useState(0);
useEffect(() => {
renderCount.current += 1; // ok
setCount(renderCount.current); // ok
}, [renderCount]);
return <div>{count}</div>
}

In total, these features make it easier for programmers and reviewers to focus on the business logic instead of writing bug-free code. Let Flow sort out the details– you can focus on impact.

Hook Syntax

Hooks are another fundamental abstraction in React that can benefit from improved type checking validation. We’ve introduced the hook keyword that can be used in place of function when declaring custom hooks. This allows flow to enforce that hooks adhere to the React programming model:

  • Hooks must be called in the same order on every render (which implies that hooks may not be called conditionally).
  • Hooks must operate under the same restrictions as component bodies, such as not reading ref.current or mutating any passed in parameters.
  • When called, hooks must obey the useFoonaming convention. For example, use_foo() or myHook() would lead to a flow error.
  • As the converse of the above, non-hook functions used within components or other hooks should not have names that match the useFoo pattern.

Many of these rules are already enforced by React’s ESLint plugin, but we also apply the validations discussed in the components section to hooks.

As you can see from the examples below, the only change necessary to adopt hook syntax is to replace function with hook.

Code Today 😴

function useOnlineStatus(initial: boolean): boolean {
const [isOnline, setIsOnline] = useState(initial);
useEffect(() => {
// ...
}, []);
return isOnline;
}

Hook Syntax 🔥

hook useOnlineStatus(initial: boolean): boolean {
const [isOnline, setIsOnline] = useState(initial);
useEffect(() => {
// ...
}, []);
return isOnline;
}

Check out the hooks documentation for more information.

Render Types

Render types are a powerful new feature providing an ergonomic way to define how components should be composed. We designed render types to make working with component libraries safer and easier. At its core, render types consist of a new renders type annotation that can be used to specify what a component ultimately renders. Here is an example:

// Component library
export component Header() { ... }

export component Layout(
header: renders Header, // Library components can specify what props should render
) { ... }


// Product code
component MyHeader() renders Header { // Components can describe what they will ultimately render
// ...
return <Header />;
}

export component MyLayout() {
// OK! MyHeader renders Header
return <Layout header={<MyHeader />} />;
}

Remember, not everyone at Meta who needs to write React code is a front-end expert. Design systems make it easy for people with relatively little experience to make beautiful cohesive UIs. Render types let the design system owners codify their design rules in the type system, which helps all the downstream users build even more delightful experiences without having to know much about UX or UI design.

With render types, you can also:

  • Describe optional rendering, or rendering a list of items with the renders? and renders* variants.
  • Use component names as shorthand for easily referring to component types. For example, Header is equivalent to React.Element<typeof Header>.

Check out the render types documentation for a lot more information on render types.

Try it out

To see Component Syntax in action or try it out for yourself, check out our example app on GitHub.

Conclusion

Component Syntax introduces a novel approach to building React UIs, offering enhanced type safety, readability, and developer efficiency. It reduces boilerplate for components, enforces React best practices, and allows developers to safely express common design-system patterns.

We designed component Syntax in coordination with the React team, and are already using it across our codebases at Meta. We plan to continue expanding Flow’s understanding of React in the future and improve safety and expressivity when working with React.

Written by Alex Taylor and Jordan Brown

--

--