Skip to main content

Flow is a typed dialect of JavaScript.

It looks like TypeScript now. Plus component / renders / match. And still safer.

01 — Familiar

Familiar syntax

Is it Flow or TypeScript? You can't tell. Flow has keyof, readonly props, unknown, indexed access types T[K], and extends generic bounds. Plus conditional types, mapped types, and type guards. More TS-compatible syntax

1type User = {2  readonly name: string,3  readonly age: number,4  readonly metadata: unknown,5};6function get<K extends keyof User>(user: User, key: K): User[K] {7  return user[key];8}9declare const user: User;10const age: number = get(user, 'age');
1type User = {2  readonly name: string,3  readonly age: number,4  readonly metadata: unknown,5};6function get<K extends keyof User>(7  user: User,8  key: K,9): User[K] {10  return user[key];11}12declare const user: User;13const age: number = get(user, 'age');
02 — React

First-class React

component and renders are built into Flow's syntax. Props are named parameters, typed inline with defaults, so there's no props object to annotate or destructure. Only components that render Header can flow into a renders Header prop: design-system composition rules become type errors instead of bugs caught in review. More on component and renders

1import * as React from 'react';2
3component Header(text: string = "Title") { return <h1>{text}</h1> }4component Footer() { return <footer /> }5
6component Layout(header: renders Header) {7  return <div>{header}</div>;8}9
10<Layout header={<Header />} />; // OK11<Layout header={<Footer />} />; // ERROR: doesn't render Header
Cannot create Layout element because in property header: Footer element [1] does not render Header [2].
1import * as React from 'react';2
3component Header(4  text: string = "Title",5) {6  return <h1>{text}</h1>;7}8component Footer() {return <footer />}9component Layout(10  header: renders Header,11) {12  return <div>{header}</div>;13}14
15<Layout header={<Header />} />; // OK16// Doesn't render Header:17<Layout header={<Footer />} />; // ERROR
Cannot create Layout element because in property header: Footer element [1] does not render Header [2].
03 — Match

Pattern matching with match

match is both an expression (shown here) and a statement: a safer switch with no fall-through. It's exhaustively checked, with structural patterns that destructure as they match. Forget a case, like 'remove', and match flags it and tells you what to add. More on match

1type Action = {type: 'add', text: string}2            | {type: 'toggle', id: string}3            | {type: 'remove', id: string};4declare const action: Action;5
6const desc = match (action) { // ERROR: 'remove' missing7  {type: 'add', const text} => `Add: ${text}`,8  {type: 'toggle', const id} => `Toggle ${id}`,9};
match hasn't checked all possible cases of the input type. To fix, add the missing pattern: {type: 'remove', id: _} to match object type [1].
1type Action =2  | {type: 'add', text: string}3  | {type: 'toggle', id: string}4  | {type: 'remove', id: string};5declare const action: Action;6
7// 'remove' missing:8const desc = match (action) { // ERROR9  {type: 'add', const text} =>10    `Add: ${text}`,11  {type: 'toggle', const id} =>12    `Toggle ${id}`,13};
match hasn't checked all possible cases of the input type. To fix, add the missing pattern: {type: 'remove', id: _} to match object type [1].
04 — Objects

Exact objects by default

Object types in Flow are exact by default. TypeScript only catches extras on direct object-literal assignment, so sample: "free" slips through once the object goes through a variable. Flow catches it at the assignment, before the crash. More on object exactness

1type Prices = {apple: number, banana: number};2const items = {apple: 1.5, banana: 0.5, sample: "free"};3
4const prices: Prices = items; // ERROR: extra property `sample`5
6Object.values(prices).map(price =>7  `${price.toFixed(2)}`, // Runtime crash! `toFixed` on "free"8);
Cannot assign items to prices because property sample is extra in object literal [1] but missing in Prices [2]. Exact objects do not accept extra props.
1type Prices = {2  apple: number,3  banana: number,4};5const items = {6  apple: 1.5,7  banana: 0.5,8  sample: "free",9};10
11// Extra property `sample`:12const prices: Prices = items; // ERROR13
14Object.values(prices).map(price =>15  // `toFixed` on "free":16  `${price.toFixed(2)}` // Runtime crash!17);
Cannot assign items to prices because property sample is extra in object literal [1] but missing in Prices [2]. Exact objects do not accept extra props.
05 — Safety

Safety by default

Pulling counter.incr off the instance detaches the method from counter, so the later tick() runs with this undefined and ++this.count throws. TypeScript types the extracted method as a plain function and waves the call through. Flow rejects the unbound method extraction at the point where this is lost. More safety differences from TypeScript

1class Counter {2  count: number = 0;3  incr(): number {4    return ++this.count;5  }6}7const counter = new Counter();8const tick = counter.incr; // ERROR: unsafe method extraction9tick(); // Runtime crash! Invalid `this` inside `incr`
Cannot get counter.incr because property incr [1] cannot be unbound from the context [2] where it was defined.
1class Counter {2  count: number = 0;3  incr(): number {4    return ++this.count;5  }6}7const counter = new Counter();8// Unsafe method extraction:9const tick = counter.incr; // ERROR10// Invalid `this` inside `incr`:11tick(); // Runtime crash!
Cannot get counter.incr because property incr [1] cannot be unbound from the context [2] where it was defined.

Use Flow in your project

Used in production at Meta across millions of files of JavaScript and React.