How to use/define Enums with Flow type checking?

Chet picture Chet · Aug 25, 2016 · Viewed 11.2k times · Source

I'm trying to migrate an existing codebase to use Flow. Since this project started without Flow, I'm using a pretty typical JS pattern for enums and such.

Here are a few definitions I want to

export const LOAN_STATUS  = {
  PENDING: 'pending',
  CURRENT: 'current',
  DUE: 'due',
  OVERDUE: 'overdue',
  PENDING_PAYMENT: 'pending_payment',
  CHARGED_OFF: 'charged_off',
  VOIDED: 'voided',
  DISPUTED: 'disputed',
  REFUNDED: 'refunded',
  SETTLED: 'settled',
}

export const ACTIVE_LOAN_STATUS = [
  LOAN_STATUS.OVERDUE,
  LOAN_STATUS.CURRENT,
  LOAN_STATUS.DUE,
  LOAN_STATUS.PENDING_PAYMENT,
]

Flow works fine until I import this file and it says I need to add type annotations. This seems odd -- why should I have to annotate objects that are entirely static and easily inferred?

Is there any way that define its type as "static" or "literal"?

So then I go about thinking how I'm going to add annotations to this. My first thought is just {[key: string]: string} and Array<string>. Flow works, but I'm realizing that these type definitions are totally worthless. So then I try this other approach:

type LoanStatusValues =
  'pending' |
  'current' |
  'due' |
  'overdue' |
  'pending_payment' |
  'charged_off' |
  'voided' |
  'disputed' |
  'refunded' |
  'settled'

type LoanStatusKeys =
  'PENDING' |
  'CURRENT' |
  'DUE' |
  'OVERDUE' |
  'PENDING_PAYMENT' |
  'CHARGED_OFF' |
  'VOIDED' |
  'DISPUTED' |
  'REFUNDED' |
  'SETTLED'

type ActiveLoanStatus = 
"current" |
"due" |
"overdue" |
"pending_payment"

And I use the type annotations {[key: LoanStatusKeys]: LoanStatusValues} and Array<ActiveLoanStatus>. But even these annotations loose the fact that this is static!

It just seems so odd that I'm having to write this much duplicate code. And then if I want to convert just to Flow I can't actually use the types in JS. For example I might do this:

if (defs.ACTIVE_LOAN_STATUS.indexOf(loan.status) !== -1) {

}

Now if I want to use Flow types, I can't do anything like this:

type ActiveLoanStatus = 
  "current" |
  "due" |
  "overdue" |
  "pending_payment"

if (loan.status isTypeOf ActiveLoanStatus) {

}

So how am I supposed to use these static enums? I must be doing this wrong!

Answer

Aleksey L. picture Aleksey L. · Mar 5, 2018

To express an enum with flow you can use $Values utility in conjunction with frozen object type:

export const LOAN_STATUS = Object.freeze({
  PENDING: 'pending',
  CURRENT: 'current',
  DUE: 'due',
  OVERDUE: 'overdue',
  PENDING_PAYMENT: 'pending_payment',
  CHARGED_OFF: 'charged_off',
  VOIDED: 'voided',
  DISPUTED: 'disputed',
  REFUNDED: 'refunded',
  SETTLED: 'settled',
});

type LoanStatus = $Values<typeof LOAN_STATUS>;

export const ACTIVE_LOAN_STATUS: LoanStatus[] = [
  LOAN_STATUS.OVERDUE,
  LOAN_STATUS.CURRENT,
  LOAN_STATUS.DUE,
  LOAN_STATUS.PENDING_PAYMENT,
]

This works starting from 0.60.0 version.