Skip to main content

Match Expressions and Statements

An experimental Flow langauge feature. See adoption for how to enable.

match an input value against a series of patterns, which conditionally check the structure of the input and extract values, and either produce an expression (match expressions) or execute a block (match statements).

You can replace switch statements using match, avoiding the problems associated with switch (like fall-through behavior) while gaining benefits like exhaustiveness checks and complex pattern support.

You can also replace nested conditional ternary expressions using match expressions, making your code much more readable while gaining the power of match.

component AgePane(
maybeAge?: {type: 'valid', age: number} | {type: 'error', msg: string}
) {
return match (maybeAge) {
{type: 'valid', const age} if (age < 0) =>
<Err>Age cannot be negative!</Err>,
{type: 'valid', const age} => <Pane>Age: {age}</Pane>,
{type: 'error', const msg} => <Err>{msg}</Err>,
undefined => <Err>Age is not defined.</Err>,
};
}

Match Expressions

Match expressions allow you to define conditional logic as an expression. They can replace nested conditional ternary expressions.

A match expression is made up of its argument and a series of cases, each which define a pattern and an expression body. Each pattern is checked in sequence, and the resulting expression is the one accompanying the matching pattern. The resulting expression is typed as the union of every case expression type.

A pattern can be followed by a guard, with the if (<cond-expression>) syntax. If the pattern matches, the expression in the guard is also executed, and the entire case only matches if the result is truthy.

Example structure:

const e = match (<arg>) {
<pattern-1> => <expression-1>,
<pattern-2> if (<cond>) => <expression-2>,
<pattern-3> => <expression-3>,
};

The guard applies to the entire pattern, including "or" patterns, e.g. 1 | 2 if (cond) will first match if the value is 1 | 2, and then finally succeed if cond is also true. Guarded cases do not count toward exhaustiveness checks, since they may or may not match based on the condition.

Match expressions cannot be used in an expression statement position, as that is reserved for match statements.

match (<arg>) {} //  This is a match statement, not a match expression

If no pattern matches, Flow will error due to a non-exhaustive match, and an exception will be thrown at runtime. You can use a wildcard (_) or variable declaration pattern (const x) as the last case of a match to catch all remaining possible matches.

Fine print: The opening brace { is required to be on the same line as the match argument match (<arg>). This way, we can introduce this feature in a way that is backwards compatible with all existing syntax: match(x); is still a call to a function called match. Prettier will automatically format match expressions in this way.

Match Statements

Match statements can replace switch statements or chained if-else statements. Similar to match expressions, they have an argument and a series of cases. The difference is each case body is a block (i.e. { ...statements... }) instead of an expression, and the construct is a statement so it does not result in a value. No break needed: the cases don’t fall-through (but you can still combine multiple patterns using "or" patterns |).

Example structure:

match (<arg>) {
<pattern-1> => {
<statements-1>;
}
<pattern-2> if (<cond>) => {
<statements-2>;
}
<pattern-3> => {
<statements-3>;
}
}

Fine print: Like match expressions, the opening brace { is required to be on the same line as the match argument match (<arg>).

Exhaustive Checking

match requires that you have considered all cases of the input. If you don't, Flow will error and tell you what patterns you could add to make the match exhaustive:

1declare const tab: 'home' | 'details' | 'settings';2
3match (tab) { // ERROR
4 'home' => {}5 'settings' => {}6}
3:1-3:5: `match` hasn't checked all possible cases of the input type. To fix, add the missing pattern: `'details'` to match string literal `details` [1]. [match-not-exhaustive]

Checks on disjoint object unions are supported.

1declare const result: {type: 'ok', value: number} | {type: 'error'};2
3match (result) { // ERROR
4 {type: 'error'} => {}5}
3:1-3:5: `match` hasn't checked all possible cases of the input type. To fix, add the missing pattern: `{type: 'ok', value: _}` to match object type [1]. [match-not-exhaustive]

It even works for nested structures:

1function getStyle(2  align: 'start' | 'end',3  position: 'above' | 'below',4) {5  return match ([align, position]) { // ERROR
6 ['start', 'above'] => 0,7 ['start', 'below'] => 1,8 ['end', 'above'] => 2,9 };10}
5:10-5:14: `match` hasn't checked all possible cases of the input type. To fix, add the missing pattern: `['end', 'below']` to match const array literal [1]. [match-not-exhaustive]

Flow will also error if a pattern is unused:

1declare const tab: 'home' | 'details' | 'settings';2
3match (tab) {4  'home' => {}5  'details' => {}6  'settings' => {}7  'invalid' => {} // ERROR
8}
7:3-7:11: This match pattern [1] is unused. The values it matches are either already covered by previous patterns, or are not part of the input type. To fix, either remove this pattern or restructure previous patterns. [match-unused-pattern]

More

Learn more about match patterns, including primitive value patterns, array and object patterns, variable declaration patterns, and “or” and “as” patterns.

You can also migrate from existing patterns like switch or conditional ternary expressions.

Adoption