Home

Awesome

Immutable.js for beginners

This documentation is trying to help people who start using immutable.js but at the same time feeling confused by the original overwhelming documentation.

Worth Mentioning: The documentation syntax of immutable.js is written with Typescript, so if you are confused, it is not your fault :)

If you are looking for basic concepts, please look at the last section Resources, I put up a few links to hook you up with some important concepts in Immutable / Immutablejs.

Join our conversation for Immutable.js for beginners on Slack

Table of Contents

Map

Assume:

import { Map } from 'immutable'

let me = Map({
  name: 'roy',
  hobby: 'reading'
})

The following will showcase basic operations for map

me.name // undefined, you can't reference the properties directly
me.get('name') // roy

me.set('name', 'iroy2000') // setting values
me.get('name') // roy ( what ??? )

// !!! Remember in immutable.js when you "mutate" the data, 
// it does not modify the original value, but it will return a new one
// -----------------------------------------------------------------------

me = me.set('name', 'iroy2000') // setting values
me.get('name') // iroy2000 ( ah !!! )

me = me.update('name', item => item.toUppercase)
me.get('name') // IROY2000

The following to show case you can merge with a plain object

let map2 = Map({
  age: '> 20'
})

let mapPlainObject = {
  gender: 'male'
}

// ImmutableJS treat JavaScript Array or Object as an Iterable
// ----------------------------------------------------------------
me.merge(map2, mapPlainObject)  // now the map includes age and gender: { "name": "IROY2000", "hobby": "reading", "age": "> 20", "gender": "male" }

Equality Checking

Note that the equality checking is applied to not just Map only but to others as well.

Assume that you have the following data structure

import { Map } from 'immutable'

let config = {
  name: 'roy',
  hobby: 'reading'
}

let me1 = Map(config)

let me2 = Map(config)


me1 == me2 // false
me1.equals(me2) // true


// If you just want to compare if they have same keys
// ----------------------------------------------------------------
map2.keySeq().equals(me.keySeq())

Note: If you don't understand what is Iterable, it is a collection of objects that allow you to iterate through ( or loop through ) with functions like map(), filter() or native js for ... in. Here is the official documentation in immutablejs on Iterable, and here is a StackOverflow link if you need more insight.

The following showcase some common cases for a map


map1.has(['name'])  // true

map1.map((value, key) => `$value is cool`)

Dealing with Nested structure

Assume you have the following structure for your map

// The following show case you can create immutable data struture 
// directly from plain js object using fromJS helper function
// Just remember "array" -> "List" while "object" --> "Map"
// ----------------------------------------------------------------

import { fromJS } from 'immutable'

let me = fromJS({
  name: 'roy',
  profile: {
    gender: 'male',
    language:'javascript'
  }
})

The following will explain how to performing deep values getting / setting

me = me.setIn(['profile', 'language'], 'awesome javascript')

// The following two "gets" are the same
// ------------------------------------------
me.get('profile').get('language') // awesome javascript
me.getIn(['profile', 'language']) // awesome javascript

me = me.mergeDeep({
  profile : {
    hobby: 'reading'
  }
})

me.getIn(['profile', 'hobby'])  // reading

me = me.updateIn(['profile', 'hobby'], item => .toUpperCase())

me.getIn(['profile', 'hobby'])  // READING

// The following will grab the profile data
// and delete the key = "hobby" entry in it
// -------------------------------------------
me.updateIn(['profile'], profile => profile.delete('hobby'))

Note fromJS is a convenience function that convert nested Objects and Arrays into immutable Map and List automatically. So if you are using fromJS, do not apply or mix Map or List as you could introduce unintentional bugs.

List

Assume you have the following data structure

In immutable.js, most of the data structure shares a lot of the same functionalities, because they are inheriting from the same data structure - Iterable

It supports javascript array-like operations, for example, pop, push, concat, shift ...

import { List } from 'immutable'

let items = List([1,2,3,4])

items = items.push(5)  // 1,2,3,4,5

items = items.pop() // 1,2,3,4

items = items.concat(5, 6)  // 1,2,3,4,5,6

items = items.shift() // 2,3,4,5,6

import { fromJS } from 'immutable'


// Remember "array" -> "List" while "object" --> "Map"
// ------------------------------------------------------

let me = fromJS({
  name: 'roy',
  friends: [
    { 
      name: 'captain america',
      properties: {
        equipment: 'shield'
      }
    },
    { 
      name: 'iron man',
      properties: {
        equipment: 'armor'
      } 
    },
    { 
      name: 'thor',
      properties: {
        equipment: 'hammer'
      }
    }
  ]
})

me.get('friends').get(0).get('name') // captain america
me.getIn(['friends', 0, 'name']) // captain america

me.get('friends').get(0).get('properties').get('equipment')  // shield
me.getIn(['friends', 0, 'properties', 'equipment']) // shield


me = me.setIn(['friends', 0, 'properties', 'equipment'], 'glove');

let hulk = {
  name: 'hulk',
  properties: {
    equipment: 'body'
  } 
}

// now you have new friends
me = me.update('friends', friends => friends.push(hulk))


let friendYouWantToKnowTheIndex = me.get('friends').findIndex(friend => {
 return friend.get('name') === 'captain america' 
})

me = me.setIn(['friends', friendYouWantToKnowTheIndex, 'equipment'], 'shield')

// How about shuffling all your friends ?
// A better version would be a custom comparator
// -----------------------------------------------
list = me.update('friends', 
    friends => friends.sortBy(() => Math.random())
)

The following show case some common use case for a List

let heroSalaryList = Immutable.List([
  {name: 'Thor', salary: 1000},
  {name: 'Iron Man', salary: 500},
  {name: 'Hawkeye', salary: 300}
])

// Iterate through the list and reduce the list to a value
// For example, add all salaries of heroes 
// ( and divide by number of heroes )
// ----------------------------------------------------------
let averageSalary = heroSalaryList.reduce((total, hero) => {   
  return total + hero.salary
}, 0) / heroSalaryList.count()

averageSalary == 600 // true

// fitler the list whose salary cannot be divided by 500
// ----------------------------------------------------------
let filteredHeroSalaryList = heroSalaryList.filter(
  hero => hero.salary % 500 > 1
)

filteredHeroSalaryList.count() // only 1 left

filteredHeroSalaryList.get(0).get('name') // Hawkeye

Other Data Structure

Set

An array ( or iterable type ) of unique elements and it will remove any duplicates.

import { Set } from 'immutable'

let set1 = Set([1,1,2,2,3,3])

set1.count() // 3
set1.toArray() // 1,2,3

// You can perform subtract, intersect and union on a set.
// But if your set is objects, in order to do that safely, 
// you will have to create variables before creating the set.

const set2 = Set(['hello', 'world', 'what', 'up']);

set2.subtract['hello']  // it will remove the value from the set

// consider the following case, which the set contains objects
const greet1 = { 'hello': 'world' };
const greet2 = { 'what': 'up' };

const greetSet = Set([greet1, greet2]);

greetSet.subtract([{'hello': 'world'}]);  // !!! It won't work !!!

greetSet.subtract([greet1]);  // It will work

Record

Record let you create a javascript class that comes with default values.

import { Record } from 'immutable'

let Villian = Record({ 
   skill: 'do very bad thing'
})

let joker = new Villian({
  skill: 'playing poker'
})

joker.toJSON() // { skill: 'playing poker' }

// It will fall back to default value
// if you remove it
// -----------------------------------------------
joker = joker.remove('skill')  

joker.toJSON() // { skill: 'do very bad thing' }

// Values provided to the constructor not found 
// in the Record type will be ignored.
// -----------------------------------------------

let batwoman = new Villian({
  hairstyle: 'cool'
})

batwoman.toJSON() // { skill: 'do very bad thing' }

Seq ( Lazy Operation )

Lazy operation means that the chain methods ( operations ) are not executed until it is requested. And it will stop the execution when the returned items fulfill the request.

import { Seq } from 'immutable'


// Note: The order of the items are important
// ------------------------------------------------------------------
var femaleHero = Seq.of(
	  {name:'thor', gender: 'male'},
	  {name:'hulk', gender: 'male'},
	  {name:'black widow', gender: 'female'}
	)
    .filter(hero => hero.gender == 'female')
    .map(hero => `${hero.name} is a ${hero.gender}`)
                              

// Only performs as much work as necessary to get the result
// ------------------------------------------------------------------
femaleHero  // Nothing will be called

// This will iterate all three items until the 
// first "true" return.  All three hero are iterated
// util we found one "female hero" to return.
// ------------------------------------------------------------------
femaleHero.get(0)  // return 'black widow is a female'


// If we change the order of the Seq, the first "true" will
// return and the rest of the operations will not even get called.
//
// For example: 
// The request of getting first "female hero" is fulfilled,
// the progrm terminates, which means the next operation of 
// `filter()` and `map()` will not even get called.
// ------------------------------------------------------------------

{name:'black widow', gender: 'female'},
{name:'thor', gender: 'male'},
{name:'hulk', gender: 'male'}

Resources

Readings

Toolings

Contributions

Pull Request are welcomed !!! Hopefully it will be a community driven effort to make other developers' life easier when learning immutable.js

PR could include more examples, better introduction, presentations, resources ... etc.