Adding Flow Types to Immutable Records

September 24, 2017

The contents of this post may be time sensitive as the libraries evolve. It was written considering Flow 0.54.1 and Immutable 3.8.1.

The problem with the way Immutable has typed Records lets it behave like an Any. Records are tricky, because you want the internal methods to be type checked correctly, like rec.set(), but we often treat Records as objects accessing their properties directly like rec.someProp

In cases where we have a Record holding reducer state, we would want property accesses to be type checked. rec.nonexistentProp should fail, and setting a variable expecting a type to rec.somPropWithOtherType should also fail, but we still want to be able to instantiate the Record with no args so we can create the store when the app starts and populate it when switching to a tab, or reset it to null values when switching away from a tab. We may want to have a set of null defaults initially, but when working in the tab have a flow type that defines those props as never being null. That way flow will type check to make sure all the places you use it those values have been set, and save you having to add conditionals in your utils everywhere to make sure things are set given utils that are only used when those props have values. The types for this Record class would be * => typedRecord.

On the other hand there are times when a state property can either be null or Record itself, eg: state.selectedRect may be null, or a Record that represents a domRect. That Record should only ever be instantiated with defined values or not exist at all. In that case we want flow to error if the shape or props you instantiate it with are of the wrong type or undefined. The types for this Record class would be recordShape => typedRecord.

An emerging method for handling Immutable Record Flow types is to use a flow comment block and actually create an instance then use typeof thatInstance to make the Record methods type check and type check to differentiate between Record instances of different classes. But that strategy ignores type checking for all the above cases.

Here's some code demonstrating an approach I arrived at to meet all the requirements of the above cases.