ImmutableJS records are immutably beautiful!
ImmutableJS records are super simple to use and provide tremendous advantages over the regular Map
. This post will introduce you to records, what are they, and how to start using them right now.
Record Properties
A Record
is similar to ImmutableJS Map
, however, it has the following unique features that make it special:
- You cannot add more keys to it once it has been constructed.
- You can define default values for new record instances.
- The properties of a Record Instance can be accessed like regular JS objects and can be destructed as well.
- You can name a record, for better debugging and error handling.
- You can extend the Record, to provide derived data from within the record.
This post will discuss all the following properties, but first, let us create our first record!
Creating a record
The Record
method returns a constructor function in which new instances could be made out of it.
const LivingCreature = new Immutable.Record({
name: 'Unknown',
species: 'Human',
age: 0,
});
const fooBar = new LivingCreature({name: 'Foo Bar', age: 24});
In this code snippet, we’ve created a Record of LivingCreature
and made a new instance called fooBar
. Note that this instance has the default species of Human
.
Descriptive Name
Records accept a second parameter for a descriptive name that appears when converting a Record to a string or in any error messages.
const NamedRecord = new Immutable.Record({ ... }, "[NAME HERE]");
Retrieving Properties
Unlike other ImmutableJS objects, records can be accessed like normal JS objects.
const {name, species} = fooBar;
fooBar.name; // Foo Bar
fooBar['species']; // Human
Replacing Values
To replace fooBar
living creature with another creature, simply *swap records*.
fooBar = new LivingCreature({
name: 'Foo Bar Junior',
species: 'Half Blood',
age: 8,
});
Updating Values
Using records, we can update multiple values at once using merge, in addition to using set
to update a single value.
fooBar.set('age', 20);
// or
fooBar.merge({
age: 25,
species: 12,
});
Adding new keys
The record throws an error if you attempt to add non-initialized keys on it. The following examples will throw an error.
const newFooBar = new LivingCreature({ status: 'its complicated' });
const mergeFooBar = fooBar.merge({ status: 'its complicated' });
Removing Keys
Records always have a value for the keys they define. Removing a key from a record simply resets it to the default value for that key.
const newFooBar = fooBar.remove('name');
console.log(newFooBar.name); // → 'Unknown'
Derived Values
One of the most powerful features that I personally love about records is their ability to derive data from within the record itself.
For example, let's say that we have a 'Cart' of two items, and their sum
. In normal cases, every time we update an item value, we have to update the sum as well. Soon you will feel that this is not the best practice;
class Cart extends Immutable.Record({ itemA: 1, itemB: 2 }) {
get sum() {
return this.itemA + this.itemB;
}
}
var myCart = new Cart();
myCart.sum; // 3
Now we can update any value, and since the sum is derived from the record properties, there is no need to worry about updating it manually.
const updatedCart = myCart.set('itemA', 5);
updatedCart.sum; // 7
Conclusion
Records provide an amazing advantage of allowing your immutable objects to be treated like normal objects, by having standard accessors and object de-structuring, hence any library or component that does not mutate objects will welcome the records like one of their own!
Additionally, since record keys must be specified when the record is created, reading the record will clarify its use and self-document its purpose. It also enforces a more strict code style since you cannot add any more keys to the record.
My team and I have been using ImmutableJS Records for a while now. I am surprised why it is usually overlooked and less popular than the standard Map
.