ImmutableJS is a library from Facebook that provides a series of immutable data structures. Always immutable. The reference to them can change but the data inside of them cannot. This means you can build predictable and reliable state models. This makes it easier to manage your application state.

More here: Immutable.js More about Immutable Data and React React.js Conf 2015 - Immutable Data and React - YouTube

Why ImmutableJS?

  • Immutable Data is faster
  • Tracking mutation and maintaining state is difficult
  • Encourages you to think differently about how data flows through your application
  • Less error prone
  • Simplified development
  • Predictable
  • Performance enhancements, Optimizations
  • Mutation tracking

Getting Started

ImmutableJS API is quite big. We will try to cover the basics and a bit more to show its power.

ImmutableJS provides many Persistent Immutable data structures including: List(), Stack(), Map(), OrderedMap(), Set(), OrderedSet() and Record().

We will cover Map() and List(), and describe the behavior of Seq() with Range().

Map()

Map()

Creates a new Immutable Map. An Object graph. Map - Immutable.js

JavaScript

const data = {
	'one': {
		title: 'One',
		value: 1
	},
	'two': {
		title: 'Two',
		value: 2
	}
}

let map = Immutable.Map(data)

get()

Returns the value associated with the provided key. Reading a value does not change the Map. get() - Immutable.js

JavaScript
map.get("one").title;
JavaScript
let obj = { 1: "one" };
Object.keys(obj); // [ "1" ]
obj["1"]; // "one"
obj[1]; // "one"

let map = Map(obj);
map.get("1"); // "one"
map.get(1); // undefined

getIn()

To get data from a deeply nested structure. getIn() - Immutable.js

With a Map()

JavaScript

let map = Immutable.fromJS({
	title: 'Todo One',
	text: 'Do todo',
	category: {
		title: 'Some category',
		order: 1
	}
})

map.getIn(['category', 'title']) // 'Some category'

length - size

To get the size of a Map() or a List()

JavaScript
map.size;

set()

JavaScript
map.set("three", { title: "three", value: 3 });

delete()

JavaScript
map.delete("three");

update()

JavaScript
map.update('one', item => '')

clear()

Returns a new Map containing no keys or values.

JavaScript
map.clear();

merge()

Returns a new Map resulting from merging the provided iterables.

JavaScript
let mapX = Immutable.Map({ a: 10, b: 20, c: 30 });
let mapY = Immutable.Map({ a: 50, b: 40, d: 60 });

mapX.merge(mapY).toObject(); // { a: 50, b: 40, c: 30, d: 60 }

Querying Methods

has

Returns a boolean if it finds the id key

JavaScript
map.has(item.id);

first

Returns the first element of a Map

JavaScript
map.first();

Iteration Methods

We can transform collections with .filter, .map, and .reduce. Use .forEach for side effects; it does not return a transformed collection.

groupBy

Groups values by the result of the callback

JavaScript
items.groupBy((item) => {
  return item.completed;
});

Working with Subsets of a Map()

slice()

Returns the last two items of a Map() slice(<from>, <to>)

JavaScript
items.slice(items.size - 2, items.size);

takeLast()

Returns the last two items of a Map()

JavaScript
items.takeLast(2);

butLast()

Returns all items except the last

JavaScript
items.butLast();

rest()

JavaScript
items.rest();

skip()

Returns a Map() skipping the first 5 items

JavaScript
items.skip(5);

skipUntil()

Returns a Map() starting at the first value that matches the predicate

JavaScript
items.skipUntil((item) => item.value === 1);

skipWhile()

Skips consecutive values that match the predicate, stopping at the first that does not.

JavaScript
items.skipWhile((item) => item.value === 1);

Equality Methods

is()

JavaScript
let mapX = Immutable.Map({ a: 10, b: 20, c: 30 });
let mapY = Immutable.Map({ a: 10, b: 20, c: 30 });

Immutable.is(mapX, mapY); // true

FromJS

Object to Map()

Creates deeply nested Map() from a plain Javascript Object

JavaScript
let object = { a: 10, b: 20, c: 30 };

Immutable.fromJS(object); // Map()

Array to List()

Creates List() from a JS Array

JavaScript
let array = [10, 20, 30];

Immutable.fromJS(array); // List()

Usage of the reviver function

The reviver function takes a key and a value. Converting JS to Map() or List()

JavaScript
let array = [10, 20, 30];

Immutable.fromJS(array, (key, value) => {
  return value.toMap();
}); // Map()

Note: the getIn will be index based instead of object based if it comes from an array

List()

Most of the Map() methods can be used with List() But there are some differences.

Differences between the Immutable Map() and List()

List() have the same methods that a JS Array has. But instead of mutating the array it returns a new one.

Usually we wouldn’t use the push method in immutable data structures but with Immutable.List()s push methods are safe to be used.

JavaScript
let list = Immutable.List();
list = list.push(3);
list.toArray(); // [3]

get() and getIn()

The get method with Map() is key based and with List() is index based.

JavaScript
// get()
let list = Immutable.List();
list = list.push(3);
list.get(0); // 3

let map = Immutable.Map();
map = map.set("active", true);
map.get("active"); // true

// getIn()
let nestedList = Immutable.fromJS([10, 20, 30, [40, 50]]);
nestedList.getIn([3, 1]); // 50

of()

We can create a List() by using the of method

JavaScript
const items = [];
const list = Immutable.List.of("red", "green", "blue");

Using the spread operator:

JavaScript
const items = ["red", "green", "blue"];
const list = Immutable.List.of(...items);

Sequences

Represents a sequence of values. Seq() - Immutable.js

  • Sequences are immutable — Once a sequence is created, it cannot be changed.
  • Sequences are Lazy

Creating sequences with of()

JavaScript
let range = Array.from({ length: 1000 }, (_, index) => index);
let sequence = Immutable.Seq.of(...range)

For Example: the following performs no work, because the resulting of the sequence values are never iterated:

JavaScript
let operations = 0;

let squared = sequence.map((num) => {
  operations++;
  return num * num;
});
operations; // 0

// Now using the sequence
squared.take(10).toArray();
operations; // 10

Once the sequence is used, it performs only the work necessary. It will return it only when you ask for them.

This is really powerful because it doesn’t produce an overflow when working with an infinite range lazily.

JavaScript
let squaredRange = Immutable.Range(1, Infinity);

squaredRange.size; // Infinity

let first1000squared = squaredRange.take(1000).map((n) => n * n);

first1000squared.size; // 1000

Seq() allows for the efficient chaining of operations

JavaScript
let squaredOdds = Immutable.Range(0, Infinity)
  .filter((n) => n % 2 !== 0)
  .map((n) => n * n)
  .take(1000);

console.log(squaredOdds.toArray());

You can find this example here: Sequences - JS Bin

Memoization with Immutable JS

Sequences are lazy and do not cache their results automatically. If we need to reuse a finite sequence, cacheResult() evaluates it once and keeps its values in memory.

JavaScript
let operations = 0;
const seq = Immutable.Range(1, Infinity)
  .take(1000)
  .map((n) => {
    operations++;
    return { value: n };
  });

seq.cacheResult();
operations; // 1000

seq.toArray();
seq.toArray();
operations; // Still 1000: the mapped values were cached

Always limit an infinite sequence before caching it.

Play with Immutable JS

JS Bin - Collaborative JavaScript Debugging