Bulletproof Enums using Immutable Records and Flow
If you use immutable-js and flow in your projects, you can have statically type-checked Enums. This means you’ll errors right in your editor and on CI when you try to access an Enum property that is misspelled or doesn’t exist.
Immutable Record
Record
is a data type that enforces a specific set of allowed string keys on its instances. Once you define what a record consists of, it’s not possible to set unexpected properties on that record instance.
const User = Record({ name: "Default" })
const user = new User()
user.get("name") // => 'Default'
user.set("name", "Alex") // => Record<{ name: 'Alex' }>
user.set("namw", "Alex") ^ typo // => throws
Record also allows access to its keys using common dot notation:
user.name // => 'Default'
Enum
Let’s create a simple Enum factory:
const createEnum = <T: Object>(items: T): Record<T> => {
const Enum = Record(items);
return new Enum();
}
const MyEnum = createEnum({ A: 'a', B: 'b' });
MyEnum.get('A') // => 'a'
MyEnum.get('C') // => undefined
^
flow throws on unexpected key
That’s fine, but sometimes I need helper methods on an enum instance. For example, sometimes I need to get an array of all of the enum’s defined items. Other times, I might need to find the item by the value
key, e.g.,:
const MyEnum = createEnum({
THING: {
value: "thing",
label: "The label for the thing",
},
})
MyEnum.fromValue("thing").label // => 'The label for the thing'
Extended Enum
Luckily, you can extend the Record
class and add your custom methods to it:
/* @flow */
import { Record } from 'immutable';
interface $EnumInterface<T: Object> extends Record<T> {
items: Function;
fromValue: Function;
}
const createEnum = <T: Object>(items: T): $EnumInterface<T> => {
class Enum extends Record(items) {
// `this` here is an instance of Record so all instance methods are available!
items = () => this.toArray();
fromValue = value => this.find(item => item.value === value);
}
return new Enum();
};
export default createEnum;
This is how it works in the end:
P.S. — The examples above work with [email protected]
. Typedefs in Immutable v4 (RC at the moment) were significantly improved, but are still in flux for the extended records. Hopefully, these issues will be resolved soon!
Follow us on Twitter: @alexfedoseev · @shakacode