Making ImmutableJS work with the advantages of TypeScript

ImmutableJS is a great library. It even has TypeScript support. But, with it, there’s no more static type checking. Let’s get it fixed ASAP…

Having been drawn to FRP and especially Cycle.js in recent weeks, ImmutableJS seemed like a godsend for managing state, as mutable state, at least according to some, is the root of all evil. In Cycle.js, your app is a pure function which takes user interaction as input and gives UI as output, with no side effects. Writing a pure function with no side effects also means you can’t hold on to any state per se. How, then, do you do state management for your app?

In Cycle.js, anything that changes is modelled as a stream, and why make an exception for state? We now react to the state$ (read state stream), which may itself react to other streams. We have the state$ representing the changing state, so we can’t have mutating state also — it just causes a lot of confusion on what constitutes a state change in the app, and what doesn’t. Also, having a stream of immutable states which can be folded (aggregated) onto a collection will get us the entire history, allowing us to time-travel.

The solution is all well and good, but how do we implement immutability, and make sure everything works? This is were ImmutableJS comes in. It is a library that allows you to work with Immutables. Think immutable objects, collections, whatever. However, there is one big problem. You use string-based property getters and setters.

When you’re working with TypeScript in a wonderful editor like Visual Studio Code, the static type checks do wonders. (If you haven’t tried it, you should. Seriously.) But when you use Map, or fromJs, everything flies out the window. You no longer have type checks, everything falls back to the dreaded any. The problem with any is that propagates across your app until everything you use shows up as any and there’s no longer any useful static type checking.

A workaround is to add types to every const that you use, but who the hell does that? Besides, if you have to specify the type every time you use something, why use TypeScript at all (though it works)? We want properties. And we want property getters with names instead of using a function with strings. In comes Record, which tries to save you. With Record, you can specify the keys allowed, and can access the initial keys directy.

All you have to do is define an interface for your type, create a Record, and then extend the Record with your class that implements the interface. Say I want to have an interface for a person. I would name it IPerson (so that I can have a Person class) and define the properties there. I would create a PersonRecord, with the default values. I would then create a class Person that extends PersonRecord and implements IPerson.

There’s still one more problem here — the constructor accepts any object, with any properties. Let’s change that. Set the type of the parameter to the constructor to be IPerson. Problem solved! You now have a typed immutable, which has property-based getters (not setters).

NOTE: The TypeScript Style Guide recommends naming interfaces without a leading I. However I feel this is a genuine case where it can be used. Strict followers of the style guide can instead use PersonBase as the interface name.

Did this work for you? Could I have done something better? Have I missed something? Please use comments to place your feedback, suggestions, whatever you feel like sharing.


Subscribe to The ArtfulDev Journal

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.