Photo by Krivec Ales from Pexels

Introducing Flow Enums

George Zahariev
Flow
Published in
3 min readSep 13, 2021

--

As announced in an earlier post, Flow will be introducing new language features. The first one is Flow Enums, an opt-in feature which allows you to define a fixed set of constants which create their own type. If you are familiar with TypeScript, you can take a look at our comparison of Flow Enums and TypeScript enums.

Flow Enums provide several benefits over existing enum-like patterns (like Object.freeze and string literal unions): reducing repetition by defining a value and type in one definition, enabling new functionality like a type-safe cast function, enhancing safety by requiring exhaustive checking in switch statements, and guaranteeing good Flow performance.

At Facebook, we’ve been using Flow Enums for over a year, and have over 5,000 Flow Enums in our codebase.

The documentation has complete information about how to enable, define and use Flow Enums. Here is a quick overview:

Overview

Unlike other features in Flow, enums exist as values at runtime, as well as existing as types.

Define an enum with string values which mirror the member names:

enum Status {
Active,
Paused,
Off,
}

Define an enum with number values:

enum Status {
Active = 1,
Paused = 2,
Off = 3,
}

Previously if using Object.freeze, you would have to define a object with values and a type separately, which is more verbose.

Access an enum member, and use the enum name as a type annotation:

const status: Status = Status.Active;

Previously, the type name and enum object name would be different, now we can have one name and one import for both type and value.

Safely cast a string to an enum member (or undefined if not a valid member):

const couldBeStatus: Status | void = Status.cast(someString);

Previously, you would have to create a switch statement with every enum member as a case in order to cast an arbitrary string/number to an enum value in a type-safe way.

Check an enum in switch statement exhaustively - we ensure you check all members:

// ERROR: Incomplete exhaustive check: the member `Off` of enum
// `Status` has not been considered in check of `status`
switch (status) {
case Status.Active: ...; break;
case Status.Paused: ...; break;
// We forgot to add `case: Status.Off:` here,
// resulting in error above
}

Flow Enums are not a syntax for union types. They are their own type, and each member of a Flow Enum has the same type. Large union types can cause performance issues, as Flow has to consider each member as a separate type. With Flow Enums, no matter how large your enum is, Flow will always exhibit good performance as it only has one type to keep track of.

Read the quickstart guide for more, and the defining enums and using enums documentation for an in-depth look.

Enabling Flow Enums

You can read the full documentation on how to enable Flow Enums in your project. In short:

Migrating Legacy Patterns

You can then migrate from legacy patterns in your codebase. For example, if before you had:

const Status = Object.freeze({
Active: 1,
Paused: 2,
Off: 3,
});
type StatusType = $Values<typeof Status>;
const status: StatusType = Status.Active;

You can use:

enum Status {
Active = 1,
Paused = 2,
Off = 3,
}
const status: Status = Status.Active;

If before you cast to the enum type like this:

function castToStatus(input: number): StatusType | void {
switch(input) {
case 1: return Status.Active;
case 2: return Status.Paused;
case 3: return Status.Off;
default: return undefined;
}
}
castToStatus(x)

You can now just use:

Status.cast(x)

More

Read the documentation for more information.

--

--