Immutable.js is intimidating. Here‘s how to get started.

Immutable.js is a powerful library for creating immutable data structures in JavaScript, but it can be intimidating when you‘re getting started. Don‘t worry though – once you understand the core concepts and APIs, you‘ll find it indispensable for building more robust and maintainable applications. Here‘s what you need to know to start using Immutable.js effectively.

What is Immutable.js?

Immutable.js is a library created by Facebook that provides a set of immutable data structures for JavaScript. An immutable data structure is one that cannot be changed once it is created. Instead of modifying the structure, you create a new structure with the changes you want. This provides several benefits:

  • Avoiding unexpected mutations and bugs caused by inadvertent changes to shared state
  • Enabling optimizations like memoization and change tracking by quickly checking if data has changed
  • Simplifying application development by making state changes more explicit and deterministic

While JavaScript provides some immutable primitive types like numbers and strings, its object types like arrays and objects are mutable by default. Immutable.js allows you to create immutable versions of these types that provide a more functional and explicitly immutable API.

Immutable.js Data Types

Immutable.js provides a number of immutable data types that mirror built-in JavaScript types:

  • Map – An unordered key-value structure, similar to a plain JavaScript object
  • List – An ordered indexed sequence of values, similar to a JavaScript array
  • Set – An unordered set of unique values
  • Stack – An ordered collection that mimics a last-in-first-out stack
  • Range – A sequence of numbers progressing from start to end
  • Repeat – A sequence that repeats a value a number of times
  • Record – Like a Map but with fixed keys and default values, useful for domain models

Each of these types are created using a corresponding factory function:

import { Map, List, Set, Stack, Range, Repeat, Record } from ‘immutable‘;

const map = Map({ a: 1, b: 2, c: 3});
const list = List([1, 2, 3]);
const set = Set([1, 2, 3]);
const stack = Stack([1, 2, 3]); 
const range = Range(1, 10);
const repeat = Repeat(‘hi‘, 100);
const ABRecord = Record({ a: 1, b: 2 });
const record = new ABRecord({ b: 3 });

Once created, you use methods to query or transform the immutable data structures. Unlike Array or Object methods in JavaScript that mutate the collection, Immutable.js methods always return a new immutable collection with the changes applied:

const map2 = map.set(‘b‘, 20);
const list2 = list.push(4);
const set2 = set.add(4); 
const stack2 = stack.unshift(0);

Converting to and from Immutable.js

To use your existing JavaScript data with Immutable.js, you need to convert it to an immutable data structure. The simplest way is to use the fromJS() function:

import { fromJS } from ‘immutable‘;

const obj = { 
  a: 1,
  b: { x: 10, y: 10 },
  c: [1, 2, 3]
};

const map = fromJS(obj);

fromJS() will recursively convert objects to Maps and arrays to Lists. If you want to do a shallow conversion, you can use the Map() or List() functions directly.

To convert an immutable structure back to plain JavaScript, use the toJS() method:

map.toJS(); // { a: 1, b: { x: 1, y: 2 }, c: [1, 2, 3] }

Again, this does a deep conversion recursively converting nested structures back to objects and arrays. For a shallow conversion, use toJSON() or toArray() instead.

Using Immutable.js Data Structures

Let‘s look at some common tasks when working with immutable data structures. We‘ll use a Map as an example, but many of the principles apply to other immutable types as well.

To retrieve a value from a Map, use the get() method:

const map = Map({ a: 1, b: 2, c: 3});
map.get(‘a‘); // 1
map.get(‘z‘); // undefined

To retrieve a nested value, use getIn() and provide a path array:

const map = fromJS({ a: { b: { c: 3 } } });  
map.getIn([‘a‘, ‘b‘, ‘c‘]); // 3
map.getIn([‘z‘, ‘z‘, ‘z‘]); // undefined 

To set a value, use set() and setIn():

const map2 = map.set(‘c‘, 30);
const map3 = map2.setIn([‘a‘, ‘b‘, ‘c‘], 100); 

Notice how these operations create new maps rather than mutating the original one. You can also update() a value based on its current value:

const map4 = map3.update(‘a‘, value => value + 1);

You can transform immutable structures using normal JavaScript functions passed to methods like map(), filter(), and reduce():

const list = List([1, 2, 3]);
const doubled = list.map(value => value * 2); // List [2, 4, 6]

Chained transformations efficiently create new immutable collections:

const list = List([1, 2, 3, 4, 5]);
const result = list
  .map(value => value * 2) 
  .filter(value => value > 5)
  .reduce((sum, value) => sum + value); // 24

When checking equality of two immutable data structures, Immutable.js does a efficient shallow comparison:

const map1 = Map({ a: 1, b: 2 });
const map2 = Map({ a: 1, b: 2 });
map1 === map2; // false - different instances
map1.equals(map2); // true - same values

For deeply nested structures, equals() will do an efficient recursive equality check.

Immutable.js with React and Redux

Immutable.js is a great fit for React and Redux applications. Using immutable data structures to represent your application state and props makes it easier to optimize rendering with shallow equality checks and implement features like undo/redo.

In Redux, your top-level state object is typically a plain JavaScript object, but you can use an immutable Map instead:

import { Map } from ‘immutable‘;

const initialState = Map({
  todos: List(),
  visibilityFilter: ‘SHOW_ALL‘
});

Your reducer functions would create new immutable state to represent changes:

function todoApp(state = initialState, action) {
  switch (action.type) {
    case ‘ADD_TODO‘:
      return state.update(‘todos‘, todos => todos.push(action.text)); 
    case ‘TOGGLE_TODO‘:
      return state.updateIn([‘todos‘, action.index, ‘completed‘], completed => !completed);
    default:      
      return state;
  }
}

To efficiently retrieve data from an immutable Redux state, use selectors:

const getTodos = state => state.get(‘todos‘);
const getVisibleTodos = createSelector(
  [ getTodos, getVisibilityFilter ],
  (todos, visibilityFilter) => {
    return todos.filter(todo => {
      return visibilityFilter === ‘SHOW_ALL‘ 
        || (todo.get(‘completed‘) && visibilityFilter === ‘SHOW_COMPLETED‘)  
        || (!todo.get(‘completed‘) && visibilityFilter === ‘SHOW_ACTIVE‘);
    });
  }
);

In your components, you can use Immutable.js data structures for props and local component state. Just remember to convert them to plain JavaScript objects when passing them to components that aren‘t expecting immutable structures.

Tips and Best Practices

Here are a few tips to keep in mind as you use Immutable.js:

  • Model your application state using a few immutable data structures at the top level, don‘t nest too deeply
  • If you need to apply multiple mutations to a immutable object, use withMutations() to avoid creating intermediate immutable objects
  • Use Seq to lazily transform large immutable collections and toSeq() to do chained transformations
  • Remember that Immutable.js structures are iterables, so you can use them with ES6 constructs like for..of and the spread operator
  • Although immutable data structures can be deeply nested, prefer shallow operations when possible for better performance

Learning More

The official Immutable.js documentation is a great resource, providing an overview of the library, API documentation, and advanced usage. There are also many great tutorials and articles on Immutable.js around the web:

If you have questions or need help, try the #immutable channel in Reactiflux, the Stack Overflow immutable.js tag, or the issues section in the Immutable.js Github repo.

Immutable.js has a learning curve, but it‘s worth the effort to add it to your JavaScript toolbox. The benefits of immutable data help you write more predictable, performant, and maintainable applications. So give it a try – happy coding!

Similar Posts