Immutable.js

What is Immutable.js?

Immutable.js is a library from Facebook which provides immutable data structures and utilities that we can introduce to our applications

It provides a series of immutable data structures like List, Stack, Map, and Set. THey are iterable and look a lot like arrays and feel like hashes, but Instead of them being mutable, they’re always immutable, meaning they don’t change from underneath you.

The reference to them can change, but the data inside them cannot, which means you can build predictable and reliable state models on top of them. It becomes a lot easier to manage your application’s state.

Why Immutable.js?

Much of what makes application development difficult is tracking mutation and maintaining state. Developing with immutable data encourages you to think differently about how data flows through your application.

This model of data flow aligns well with the architecture of React and especially well with an application designed using the ideas of Flux.

Values Not Objects

Immutable collections should be treated as values rather than objects. While objects represents some thing which could change over time, a value represents the state of that thing at a particular instance of time.

NPM

We can install Immutable.js using npm.

npm install immutable

Then we can require it into our module.

var Immutable = require('immutable');

var car = Immutable.Map({wheels:4});
var truck = cars.set('wheels', 6);
car.get('wheels'); // 4
truck.get('wheels'); // 6

As you can see map1‘s value remains intact.

Browser

You can download immutable.min.js file and reference it or you can use a CDN.

<script src="immutable.min.js"></script>
<script>
  var Immutable = require('immutable');

  var car = Immutable.Map({wheels:4});
  var truck = cars.set('wheels', 6);
  car.get('wheels'); // 4
  truck.get('wheels'); // 6
</script>

Manage Application State with Immutable.js

Let’s look at how Immutable.js data structures are different from native iterable JavaScript data types and why they provide an excellent foundation on which to build your application’s state.

Here we have a couple of tests. In the first test, we should see a side effect when mutating this original array. We have our application state. We’ve added two todos to it. We passed a reference of this state to this variable right here.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Learning Immutable.js</title>

  <meta name="description" content="Navigating and Querying an Immutable.js Map()">

  <link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/mocha/2.3.3/mocha.min.css"></link>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/mocha/2.3.3/mocha.min.js"></script>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/chai/3.3.0/chai.js"></script>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/immutable/3.7.5/immutable.min.js"></script>
  <script src="http://cdn.rawgit.com/lodash/lodash/3.0.1/lodash.min.js"></script>

</head>
<body>

  <div id="mocha"></div>
  <script src="index.js"></script>
</body>
</html>

The JavaScript file should look like the following:

mocha.setup('bdd');
var expect = chai.expect;

// this will take a position and update it at that place
function mutateValue(iterable, pos, value) {
  iterable[pos] = value;
}

// attempt to update state
function updateState(immutable, pos, value) {
  return immutable.set(pos, value);
}

describe('Manage Application State with Immutable.js', function() {

  it('should see side effects when mutating original array', function() {

    var state = ["todo1", "todo2"];
    var mutatedState = state; // pass in a reference

    mutateValue(mutatedState, 0, "newTodo");
    expect(state[0]).to.not.equal("todo1");

  });

  it('should avoid side effects when mutating original array', function() {

    var immutableState = Immutable.List(["todo1", "todo2"]);
    var immutableState2 = immutableState;

    updateState(immutableState2, 0, "newTodo");
    expect(immutableState.get(0)).to.equal("todo1");

  });

});

mocha.run();

In the second test we will go ahead and update the state and see what happens. We’ll do the same thing, newTodo, and see what happens. We should expect, just like the first test, that immutableState, the first one, .get(0), should equal newTodo.

Modifying an Immutable.js Map

Let’s look at the methods that modify an Immutable Map.

  • set
  • delete
  • clear
  • update
  • merge

To demonstrate the use of these functions, let’s look at an example.

mocha.setup('bdd');
var expect = chai.expect;

var Todo = function(title, text, completed) {
    this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
    this.title = title;
    this.text = text;
    this.completed = completed;
};

function addTodo(todos, todo) {
  return todos.set(todo.id, todo);
}

function removeTodo(todos, todo) {
  return todos.delete(todo.id, todo);
}

function updateTodo(todos, todo) {
  return todos.update(todo.id, function(todo) { return todo; });
}

function mergeTodos(todos, todos2) {
  return todos.merge(todos2);
}

function clearAll(todos) {
  return todos.clear();
}

describe('Modifying an Immutable.js Map()', function() {

  it('should add todo to state', function() {

    var todo = new Todo("Todo 1", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo);

    expect(todos.get(todo.id)).to.equal(todo);

  });

  it('should remove todo from state', function() {

    var todo = new Todo("Todo 1", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo);

    expect(todos.get(todo.id)).to.equal(todo);

    todos = removeTodo(todos, todo);
    expect(todos.get(todo.id)).to.be.undefined;

  });

  it('should update todo', function() {

    var todo = new Todo("Todo 1", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo);

    todo.title = "New Title";

    todos = updateTodo(todos, todo);
    expect(todos.get(todo.id).title).to.equal("New Title");

  });

  it('should remove all todos', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo " + index, "I'm a todo!", false));
    });

    expect(todos.size).to.equal(10);

    todos = clearAll(todos);
    expect(todos.size).to.equal(0);

  });

  it('should merge todos', function() {

    var todos = Immutable.Map();
    var todos2 = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo " + index, "I'm a todo!", false));
    });

    _.each(_.range(10), function(index) {
      todos2 = addTodo(todos2, new Todo("Todo " + index, "I'm a todo!", false));
    });

    todos = mergeTodos(todos, todos2);
    expect(todos.size).to.equal(20);

  });

});

mocha.run();

Those are four pure functions that apply the Set, Delete, Update, Merge and Clear methods to an immutable map, and return the new reference.

This is key, since immutable data cannot be mutated, it must return a new reference to which the application will then refer.

Querying Immutable.js Map

Let’s find out how to query an Immutable.Map() using get, getIn, has, includes, find, first and last.

These are powerful operators that make finding data in an object graph pain free.

mocha.setup('bdd');
var expect = chai.expect;

function Todo(title, text, completed) {
  this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  this.title = title;
  this.text = text;
  this.completed = completed;
}

function findTodo(todos, todo) {
  return todos.find(function(t) {
    return t.id === todo.id;
  }, null, null);
}

function addTodo(todos, todo) {
  return todos.set(todo.id, todo);
}

describe('Querying an Immutable.js Map()', function() {

  it('should properly report keys', function() {

    var todo = new Todo("Todo 1", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo);

    expect(todos.get(todo.id)).to.equal(todo);
    expect(todos.has(todo.id)).to.equal(true);
    expect(todos.has("unknown key")).to.equal(false);

  });

  it('should properly report included values', function() {

    var todo1 = new Todo("Todo 1", "I'm a todo!", false);
    var todo2 = new Todo("Todo 1", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo1);

    expect(todos.includes(todo1)).to.equal(true);
    expect(todos.includes(todo2)).to.equal(false);

  });

  it('should find nested keys', function() {

    var todos1 = Immutable.Map();
    var todos2 = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos1, new Todo("Todo" + index, "I'm a todo!", false));
    });

    _.each(_.range(10), function(index){
      todos1 = addTodo(todos2, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var multipleTodoStates = Immutable.Map({
      "todo1": todos1,
      "todo2": todos2
    });

    var todoID = todos1.first().id;

    expect(multipleTodoStates.getIn(["todo1", todoID], null)).to.equal(todos1.first());


  });

  it('should find todo', function() {

    var todo1 = new Todo("Todo 1", "I'm a todo!", false);
    var todo2 = new Todo("Todo 2", "I'm a todo!", false);

    var todos = Immutable.Map();
    todos = addTodo(todos, todo1);
    todos = addTodo(todos, todo2);

    expect(findTodo(todos, todo1)).to.equal(todo1);

  });



});

mocha.run();

Now you’ve learned how you can query an unordered Immutable Map with a few useful methods, let’s move on to the next part, iterarting an immutable map.

Iterating Over an Immutable.js Map

Immutable.js provides several methods to iterate over an Immutable.Map(). These also apply to the other immutable structures found within the Immutable.js family, such as Set and List. The primary methods are map and forEach, but we will also cover filter and groupBy.

mocha.setup('bdd');
var expect = chai.expect;

function Todo(title, text, completed) {
  this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  this.title = title;
  this.text = text;
  this.completed = completed;
}

function getTodoTexts(todos) {
  return todos.map(function(todo) {
    return todo.text;
  });
}

function markAllTodosAsComplete(todos) {
  return todos.forEach(function(todo) {
    todo.completed = true;
  });
}

function getCompletedTodos(todos) {
  return todos.filter(function(todo) {
    return todo.completed;
  });
}

function groupTodosByCompleted(todos) {
  return todos.groupBy(function(todo) {
    return todo.completed;
  });
}

function addTodo(todos, todo) {
  return todos.set(todo.id, todo);
}

describe('Iterating over an Immutable.js Map()', function() {

  it('should convert all todos into a map() of titles', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var todoTexts = getTodoTexts(todos);

    expect(todoTexts.first()).to.equal("I'm a todo!");

  });

  it('should filter todos', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", index % 2 === 0));
    });

    filteredTodos = getCompletedTodos(todos);

    expect(filteredTodos.size).to.equal(5);

  });

  it('should mark all todos completed', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    // This has the chance for side effects
    markAllTodosAsComplete(todos);

    _.each(todos.toArray(), function(todo) {
      expect(todo.completed).to.be.true;
    });

  });

  it('should group todos by completed boolean', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index) {
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", index % 2 === 0));
    });

    groupedTodos = groupTodosByCompleted(todos);

    expect(groupedTodos.get(true).size).to.equal(5);
    expect(groupedTodos.get(false).size).to.equal(5);

  });

});

mocha.run();

Well, as we can see, we have one test that says, give me the group todos that have a key of true, and give me their size. We have a size of five, because there are only five completed todos, and the same for thing for the false. We have five incomplete todos, and now you see all the tests pass.

Working with Subsets of an Immutable.js Map

Immutable.js offers methods to break immutable structures into subsets much like Array for instance it has the all powerful slice() and unlike Array it offers functional methods like take() and skip(). You get the best of both the imperative and functional worlds.

mocha.setup('bdd');
var expect = chai.expect;

function Todo(title, text , completed) {
    this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
    this.title = title;
    this.text = text;
    this.completed = completed;
}

function addTodo(todos, todo) {
  return todos.set(todo.id, todo);
}

function retrieveFinalPair(todos) {
  return todos.slice(todos.size-2, todos.size);
  // Alernatively, you can use this terser syntax
  //return todos.slice(-2);
}

function removeLastEntry(todos) {
  return todos.slice(0, -1);
}

function removeFirstEntry(todos) {
  return todos.slice(1, todos.size);
}

function removeFirstFive(todos) {
  return todos.skip(5);
}

function findMeMonkey(todos) {
  return todos.skipUntil(function(todo) { return todo.text === "monkey"; });
}

function stopAtMonkey(todos) {
  return todos.skipWhile(function(todo) { return todo.text === "monkey"; });
}

describe('Working with Subsets of an Immutable.js Map()', function() {

  it('should retrieve last two entries using slice()', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index){
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var lastTwoTodos = retrieveFinalPair(todos);

    expect(lastTwoTodos.size).to.equal(2);

    todos.takeLast(2).forEach(function(todo) {
      expect(lastTwoTodos.get(todo.id)).to.equal(todo);
    });

  });

  it('should remove last entry using negative slice()', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index){
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var todosWithoutLast = removeLastEntry(todos);

    todos.butLast().forEach(function(todo) {
      expect(todosWithoutLast.get(todo.id)).to.equal(todo);
    });

  });

  it('should remove first entry using slice()', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index){
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var todosWithoutFirst = removeFirstEntry(todos);

    todos.rest().forEach(function(todo) {
      expect(todosWithoutFirst.get(todo.id)).to.equal(todo);
    });

  });

  it('should return last 5 todos using skip()', function() {

    var todos = Immutable.Map();

    _.each(_.range(10), function(index){
      todos = addTodo(todos, new Todo("Todo" + index, "I'm a todo!", false));
    });

    var lastFive = removeFirstFive(todos);

    todos.takeLast(5).forEach(function(todo) {
      expect(lastFive.get(todo.id)).to.equal(todo);
    });

  });

  it('should return todos after reaching \"monkey\" using skipUntil()', function() {

    var texts = ["dog", "cat", "frog", "monkey", "octopus", "horse", "orangutan"];
    var todos = Immutable.Map();

    _.each(_.range(texts.length), function(index){
      todos = addTodo(todos, new Todo("Todo" + index, texts[index], false));
    });

    var monkeyAndAfter = findMeMonkey(todos);

    todos.takeLast(4).forEach(function(todo) {
      expect(monkeyAndAfter.get(todo.id)).to.equal(todo);
    });

  });

  it('should return todos up to reaching \"monkey\" using skipWhile()', function() {

    var texts = ["dog", "cat", "frog", "monkey", "octopus", "horse", "orangutan"];
    var todos = Immutable.Map();

    _.each(_.range(texts.length), function(index) {
      todos = addTodo(todos, new Todo("Todo" + index, texts[index], false));
    });

    var upToMonkey = stopAtMonkey(todos);

    todos.takeLast(4).forEach(function(todo) {
      expect(upToMonkey.get(todo.id)).to.equal(todo);
    });
  });

});

mocha.run();

Immutable JS offers methods to break immutable structures into subsets much like array. For instance, it has the all-powerful slice, and unlike array, it offers functional methods like take and skip. You get the best of both the imperative and functional worlds.

Equality Checking with .is() and More

Equality checking deeply nested objects is painful. Fortunately Immutable.js makes this task easy with its .is() and .isSubset() methods. Let’s see how we can take two different Immutable Maps() and check for equality.

mocha.setup('bdd');
var expect = chai.expect;

function Todo(title, text, completed) {
    this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
    this.title = title;
    this.text = text;
    this.completed = completed;
}

function addTodo(todos, todo) {
  return todos.set(todo.id, todo);
}

describe('Equality Checking with .is() and More', function() {

  it('should find different maps equal if keys and values are the same', function() {

    var map1 = Immutable.Map({a:1, b:1, c:Immutable.List.of(1)});
    var map2 = Immutable.Map({a:1, b:1, c:Immutable.List.of(1)});

    expect(map1).to.not.equal(map2);
    expect(Immutable.is(map1, map2)).to.be.true;

  });

  it('should be equal if subset is equal', function() {

    var map1 = Immutable.Map({a:1, b:1});
    var map2 = Immutable.Map({a:1, b:1, c:3});

    expect(map1.isSubset(map2)).to.be.true;
    expect(map2.isSubset(map1)).to.not.be.true;

  });

  it('should be equal if superset is equal', function() {

    var map1 = Immutable.Map({a:1, b:1});
    var map2 = Immutable.Map({a:1, b:1, c:3});

    expect(map2.isSuperset(map1)).to.be.true;
    expect(map1.isSuperset(map2)).to.not.be.true;

  });


});

mocha.run();

Equality checking deeply nested objects is painful. Fortunately, immutable.js makes this task easy with its is and is subset methods. Let’s see how we can take two different immutable maps and check for their equality. Create a constant of map 1 and make it an immutable map with a few keys.

Using fromJS() to Convert Plain JavaScript Objects into Immutable Data

Immutable.js offers the fromJS() method to build immutable structures from objects and array. Objects are converted into maps. Arrays are converted into lists. The fromJS() method can also take a reviver function for custom conversions.

mocha.setup('bdd');
var expect = chai.expect;

describe('Using fromJS() to Convert Plain JavaScript Objects into Immutable Data', function() {

  it('should create deeply nested Map() from a plain javascript object', function() {

    var plainJSObject = {
      title: "Go to grocery",
      text: "I need milk and eggs",
      completed: false,
      category: {title: "House Duties", priority: 10}
    };

    var immutableTodo = Immutable.fromJS(plainJSObject);

    expect(Immutable.Map.isMap(immutableTodo)).to.be.true;
    expect(immutableTodo.getIn(["category", "title"])).to.equal("House Duties");

  });

  it('should create deeply nested List() from a plain javascript array', function() {

    var plainJSArray = [
      "Go to grocery",
      "Buy milk and eggs",
      "Help kids with homework",
      ["Buy Lemons", "Make Lemonade"]
    ];

    var immutableTodoList = Immutable.fromJS(plainJSArray);

    expect(Immutable.List.isList(immutableTodoList)).to.be.true;
    expect(immutableTodoList.getIn([3, 1])).to.equal("Make Lemonade");

  });

  it('should use reviver to generate Map() instead of List() from a plain javascript array', function() {

    var plainJSArray = [
      "Go to grocery",
      "Buy milk and eggs",
      "Help kids with homework",
      ["Buy Lemons", "Make Lemonade"]
    ];

    var immutableTodo = Immutable.fromJS(plainJSArray, function(key, value) {
      return value.toMap();
    });

    expect(Immutable.Map.isMap(immutableTodo)).to.be.true;
    expect(immutableTodo.getIn([3, 1])).to.equal("Make Lemonade");

  });

});

mocha.run();

Differences between the Immutable.js Map() and List()

The Immutable.js Map() is analogous to a Javascript Object or Hash since it is comprised of key-value pairs. The Immutable.js List() is analogous to a Javascript Array and contains many of the same native methods. Let’s compare the two and dive into the basics of List().

mocha.setup('bdd');
var expect = chai.expect;

function Todo (title, items, completed) {
  this.id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);
  this.title = title;
  this.items = items;
  this.completed = completed;
}

function addTodoToMap(todos, todo) {
  return todos.set(todo.id, todo);
}

function addTodoToList(todos, todo) {
  return todos.push(todo);
}


describe('Differences between the Immutable.js Map() and List()', function() {

  it('should find same todo in List() and Map()', function() {

    var todo = new Todo("Todo 1");

    var todosMap = Immutable.Map();
    todosMap = addTodoToMap(todosMap, todo);

    var todosList = Immutable.List();
    todosList = addTodoToList(todosList, todo);

    expect(todosMap.get(todo.id)).to.equal(todo);
    expect(todosList.get(0)).to.equal(todo);

  });

  it('should create List() from series of values', function() {

    var todoItems = ["Milk", "Eggs", "Detergent", "Bread", "Steak"];
    var list = Immutable.List.of("Milk", "Eggs", "Detergent", "Bread", "Steak");

    var count = 0;
    _.each(todoItems, function(item)  {
      expect(list.get(count)).to.equal(item);
      count++;
    });

  });


  it('should remove last element from List()', function() {

    var todoItems = ["Milk", "Eggs", "Detergent", "Bread", "Steak"];
    var list = Immutable.List(todoItems);

    list = list.pop(); // Just like Array

    var count = 0;
    _.each(todoItems, function(item) {
      if (count < 4)
        expect(list.get(count)).to.equal(item);
      else
        expect(list.get(count)).to.not.equal(item);

      count++;
    });

  });

});

mocha.run();

Sequences and Range() in Immutable.js

Understanding Immutable.js’s Map() and List() structures will likely take you as far as you want to go with immutable programming. They have only small semantic differences between each other and the remaining structures in the Immutable.js family. Sequence, however, has one major difference: it’s lazy–which opens a new realm of functional possibilities.

mocha.setup('bdd');
var expect = chai.expect;

function generateFibonacci(num) {
  console.log(this.generateFibonacci);
  return num <= 1 ? 1 : this.generateFibonacci(num-2) + this.generateFibonacci(num-1);
}

describe('Exploring Sequences and Range() in Immutable.js', function() {

  it('should see Seq() act like an Iterable', function() {

    var range = _.range(1000);
    var numberOfOperations = 0;

    var sequence = Immutable.Seq(range);

    expect(sequence.get(0)).to.equal(0);

  });

  it('should see that Seq() is lazy', function() {

    var range = _.range(1000);
    var numberOfOperations = 0;

    var powerOfTwo = Immutable.Seq(range)
      .map(function(num) {
        numberOfOperations++;
        return num * 2;
      });

    expect(numberOfOperations).to.equal(0);

    powerOfTwo.take(10).toArray(); // compute total lazily

    expect(numberOfOperations).to.equal(10);

  });

  it('should not produce an overflow with infinite Range()', function() {

    var powerOfTwoRange = Immutable.Range(1, Infinity);

    expect(powerOfTwoRange.size).to.equal(Infinity); // whoa

    first1000Powers = powerOfTwoRange
      .take(1000)
      .map(function(n) { return n * 2; } );

    expect(first1000Powers.size).to.equal(1000);

  });

  it('should demonstrate chaining with Seq()', function() {

    var oddPowerOfTwos = Immutable.Range(0, Infinity)
      .filter(function(n) { return n % 2 !== 0; })
      .map(function(n) { return n * 2; });

    first1000OddPowers = oddPowerOfTwos.take(1000);
    expect(first1000Powers.size).to.equal(1000);

  });

  it('should cache results of Seq()', function() {

    var objects = Immutable.Range(0, 1000).map(function() { return {}; });
    var take100 = objects.take(100).toArray();
    var take100Again = objects.take(100).toArray();

    take100.forEach(function(obj, index) {
      expect(obj === take100Again[index]).to.be.false;
    });

    var cachedObjects = Immutable.Range(0, 1000).map(function() { return {}; }).cacheResult();

    expect(cachedObjects.size).to.equal(1000);

    var take100Cached = cachedObjects.take(100).toArray();
    var take100CachedAgain = cachedObjects.take(100).toArray();

    take100Cached.forEach(function(obj, index){
      expect(obj === take100CachedAgain[index]).to.be.true;
    });

  });

  it('should memoize results of Seq()', function() {

    var objects = Immutable.Range(0, 1000).map(function() { return {}; });

    var take100 = objects.take(100).toArray();
    var take100Again = objects.take(100).toArray();

    take100.forEach(function(obj, index) {
      expect(obj === take100Again[index]).to.be.false;
    });

    var memoizedObjects = Immutable.Range(0, Infinity).map(_.memoize(function() { return {}; }));

    expect(memoizedObjects.size).to.equal(Infinity); // this should be impossible!

    var take100Memoized = memoizedObjects.take(100).toArray();
    var take100MemoizedAgain = memoizedObjects.take(100).toArray();

    take100Memoized.forEach(function(obj, index) {
      expect(obj === take100MemoizedAgain[index]).to.be.true;
    });
  });
});

mocha.run();

Converting Immutable.js Structures to Javascript and other Immutable Types

Immutable.js provides several conversion methods to migrate one structure to another. Each Immutable.js class contains a prefixed “to” method like Map.toList(), Map.toSet(), etc. Converting these types sometimes results in a loss of data, as we will see when converting from Map to List.

mocha.setup('bdd');
var expect = chai.expect;

describe('Converting Immutable.js Structures to Javascript and other Immutable Types', function() {

  it('should convert Map() to List()', function() {

    var map = Immutable.Map({
      key1: 'First Item',
      key2: 'Second Item'
    });

    var convertedList = map.toList();

    expect(Immutable.List.isList(convertedList)).to.be.true;

    // Keys are discarded
    expect(convertedList.first()).to.equal('First Item');
    expect(convertedList.last()).to.equal('Second Item');

  });

  it('should convert List() to Map()', function() {

    var list = Immutable.List.of('First Item', 'Second Item');

    var convertedMap = list.toMap();

    // Converted keys ascend numerically
    keys = convertedMap.keys();
    expect(keys.next().value).to.equal(0);
    expect(keys.next().value).to.equal(1);

    expect(Immutable.Map.isMap(convertedMap)).to.be.true;

    expect(convertedMap.first()).to.equal('First Item');
    expect(convertedMap.last()).to.equal('Second Item');

  });

  it('should convert Map() to Javascript Array', function() {

    var map = Immutable.Map({
      key1: 'First Item',
      key2: 'Second Item',
      key3: {key4: 'Nested Item'}
    });

    var arr = map.toArray();

    // Keys are discarded
    expect(arr[0]).to.equal('First Item');
    expect(arr[1]).to.equal('Second Item');
    expect(arr[2].key4).to.equal('Nested Item');

  });

  it('should convert Map() to JSON', function() {

    var map = Immutable.Map({
      key1: 'First Item',
      key2: 'Second Item',
      key3: {key4: 'Nested Item'}
    });

    var json = map.toJSON();

    expect(json.key1).to.equal('First Item');
    expect(json.key2).to.equal('Second Item');
    expect(json.key3.key4).to.equal('Nested Item');

  });

});

mocha.run();

Conclusion

In this article we learnt Learn how Immutable.js data structures are different from native iterable Javascript data types and why they provide an excellent foundation on which to build our application’s state.

You can find the source code for this article in Github.

Be first to comment

Leave a Reply