Branching in JavaScript can be a pain. switch? Full of footguns. Nested conditional ternary expressions? Unreadable. Initializing a let over a bunch of if/else? Verbose. That weird chain of && in your JSX? No thanks. And none of these handle complex structures or check if you handled every case.
If you haven’t looked at Flow recently: its syntax has grown so close to TypeScript that most code is hard to tell apart (here’s a side-by-side for TypeScript users). But Flow isn’t just type annotations: it adds first-class language features built for safety.
match expressions and statements are the newest. They branch on a value: the first pattern that matches maps to a result. match destructures as it matches, and it's exhaustively checked. If you forget a case of the input, Flow tells you what's missing and prevents that bug from hitting production.
An expression, not just a statement
switch is a statement. It can't produce a value, so the moment you want one you're back to a let you assign across cases, or a conditional ternary chain. match is both a statement and an expression. As an expression, it gives you the value directly:
const description = match (action) {
{type: 'add', const text} => `Add: ${text}`,
{type: 'toggle', const id} => `Toggle ${id}`,
{type: 'remove', const id} => `Remove ${id}`,
};
Each pattern matches and extracts values at the same time. {type: 'add', const text} checks that type is 'add' and binds text in one step, no separate destructuring.
Exhaustively checked by default
Drop a case and Flow tells you exactly which one is missing:
type Action =
| {type: 'add', text: string}
| {type: 'toggle', id: string}
| {type: 'remove', id: string};
declare const action: Action;
const description = match (action) {
{type: 'add', const text} => `Add: ${text}`,
{type: 'toggle', const id} => `Toggle ${id}`,
// ERROR: add the missing pattern `{type: 'remove', id: _}`
};
Add a new variant to Action during a later refactor, and every match that doesn't handle it lights up. Delete a variant and Flow points at the now-dead patterns to remove.
A better switch
When you want a statement, match is a switch without the footguns. Cases don't fall through, so there's no break to forget. Each case is its own block, so use those lets and consts without worry. And just like the expression form, it's exhaustively checked.
// Before
switch (command) {
case 'delete':
case 'remove':
data.pop();
break;
case 'add':
data.push(1);
break;
default:
show(data);
}
// After
match (command) {
'delete' | 'remove' => {
data.pop();
}
'add' => {
data.push(1);
}
_ => {
show(data);
}
}
'delete' | 'remove' is an or-pattern: one block for several patterns. _ is the wildcard, matching anything left over.
Patterns go deep
Patterns aren’t limited to single values. They reach into nested objects and arrays, binding as they go:
const name = match (entity) {
{type: 'user', details: {const name}} => name,
{type: 'page', pageInfo: {const pageName}} => pageName,
{type: 'employee', const preferredName} => preferredName,
};
They match arrays and tuples by shape, so you can branch on several values at once. Exhaustiveness works here too: leave a combination out and Flow tells you the fix.
const style = match ([align, position]) {
['start', 'above'] => styles.alignTopLeft,
['start', 'below'] => styles.alignBottomLeft,
['end', 'above'] => styles.alignTopRight,
// ERROR: add the missing pattern `['end', 'below']`
};
Conditional React rendering, finally readable
Conditional rendering in React is exactly what match expressions are for:
function Page({tab}: {tab: 'home' | 'details' | 'settings'}) {
return match (tab) {
'home' => <Home />,
'details' => <DetailsPane />,
'settings' => <Settings />,
};
}
No ternary nest, no stray &&, and if a new tab is added to the union, Flow makes you handle it.
Try it
match brings exhaustiveness, destructuring, and readable branching together in one construct. It's the kind of language-level feature Flow exists to add: not just describing the JavaScript you already write, but giving you better JavaScript to write.
It’s available now.
- Read the docs on
match, includingifguards,aspatterns, and more - Moving off
switch? See the migration guide - Coming from TypeScript? See Flow for TypeScript Users
- Try it live in the Flow playground
Top comments (0)