⚠️ This lesson is retired and might contain outdated information.

Modifying an Immutable.js Map()

J.S. Leonard
InstructorJ.S. Leonard
Share this video with your friends

Social Share Links

Send Tweet
Published 9 years ago
Updated 2 years ago

We will now look at five methods that modify an Immutable.Map(). I highly encourage you to visit the Immutable.js documentation where I am now. They are set, delete, clear, update and merge. These are used often, so let's get to know them well.

[00:00] We will now look at five methods that modify and immutable map. I highly encourage you to visit the immutable JS documentation where I am now. They are Set, Delete, Clear, Update and Merge. These are used often, let's get to know them well.

[00:18] In this JS bin, I've gone ahead and created a TODO class, which we will use in our application state. Here are four pure functions that apply the Set, Delete, Update, Merge and Clear methods to an immutable map, and return the new reference.

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

[00:43] In the first test, let's add a simple TODO. Let's go ahead and create the map. Let TODOs equal immutable.map, we'll make it empty, because we're going to add a TODO to it.

[00:55] The TODOs will be added to by applying the addto method to the TODOs, with the new TODO. This should add the TODO to the TODOs method, which passes the reference back to the new variable, we've overwritten our state. Now, let's write a quick expectation to see if the first ID in the TODOs equals the original TODO.

[01:20] We'll do expect TODOs.get, TODO.ID to equal TODO. There you have it, a passing test. An ID is generated when the TODO is instantiated, and now, we are looking that up inside of the immutable map and seeing that it's equal to TODO. That's awesome.

[01:47] Let's run this second test. We should be able to remove a TODO from state, as well. Let's go ahead and copy this, and instantiate a map. Then, we're going to go ahead and add a TODO, just like we did before, copy, paste. Now, we should be able to write the same expectation before and see this pass, which we do.

[02:11] Let's go ahead and remove it, and see what happens. We'll go TODOs equals remove TODO, and see what happens. Now, we're seeing this fail, because the new TODO has been deleted. We say not equal, and we're good to go. Let's update a TODO now. We do what we do before, go ahead and instantiate an immutable map.

[02:37] We're going to add a TODO to it. We're going to update the title on the original TODO, which will not update inside of our immutable map, because it's immutable. Then we'll just go ahead and do an update. TODOs equals update TODO, TODOs, TODO. Lots of TODOs.

[02:57] What that will do, is it will run the update method. In this update method, we pass in a function that says, "What should this new ID, what should this new key look like?" And return that value to me. We're simply just updating with whatever I pass into the function, we're going to return the exact same TODO.

[03:20] We've updated the TODO, we should see, "New Title" on the first item. Let's go ahead and try that. Here, we want to see if that title.tile is equal to new title, and we have a passing test. Now, let's go ahead and remove all of the TODOs. We're going to do something a little bit different.

[03:43] We're going to create 10 TODOs, and I'm going to paste this in here to save some time. We're going to create 10 TODOs and add them to the map. We've just added them to the map there. Now, we should be able to expect the TODOs.size to equal 10.

[04:01] I'm going to go ahead and get rid of this. We see that there are, indeed 10, but we want to actually remove them all now. Let's go ahead and go, TODOs equals clear all TODOs. Let's take a look at that method real quick. Clear all just runs the clear operation on the immutable data.

[04:26] Now, this is no longer passing, we should see zero, and boom, all of the TODOs have been removed. Pretty convenient. A final method I want to go over is the merge. Let's have two immutable maps. We have TODOs, and TODOs2.

[04:43] Let's go ahead and create 10 TODOs, each using the same lodash each and range as before. Now, we've got TODOs and TODOs2, having added 10 to each.

[05:00] Now, let's go ahead and merge them, and see where we land. We'll say TODOs equals merge TODOs, TODOs1, TODOs2. Now, TODOs.size should equal 20. There you have it, we've merged both those TODOs.

Nils
Nils
~ 8 years ago

i don't get why you wrapped every standard function if the purpose is to learn them by heart, especially since you changed the name of them.

Nils
Nils
~ 8 years ago

found and error.

it('should update todo', () => {

    const todo = new Todo("Todo 1", "I'm a todo!", false);
    
    let todos = Immutable.Map();
    
    todos = addTodo(todos, todo);
    
    todo.title = "New Title";
    
   // todos = todos.update(todo.id,todo => todo);

    expect(todos.get(todo.id).title).to.equal("New Title");
    
  }); 

https://jsbin.com/vugupiyico/1/edit?js,output Also passes, is it because the actual object saved in the map is not a Immutable object, just the map ?

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Hi Nils! They are wrapped in explicitly named functions to express their intent. addTodo() does what it says, which makes it easier for other programmers to read--this is a personal preference as I tend to separate concerns even in simple matters. I'm sorry if that was confusing.

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

You are right about the error! Since I didn't use an immutable structure inside the parent immutable object, mutating the object inside it resulted in a side effect. Here, I created a bin to illustrate: https://jsbin.com/xugefa/1/edit?js,output

Uncomment the line to make the test pass. Does that make sense?

JMBattista
JMBattista
~ 8 years ago

I'd find this more useful if we weren't wrapping the map methods inside other functions. We're getting more exposure to the helper function than the actual library methods.

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Hey JM! Sorry that wrapping the functions isn't as useful for you. I'll keep that in mind for future lessons. Thanks for the feedback!

Sandeep S Kumar
Sandeep S Kumar
~ 8 years ago

In todos.delete(todo.id, todo) second param todo is not required, is it?

compile
compile
~ 8 years ago

Hi get error in chrome console:

Uncaught SyntaxError: Unexpected token = It points to line 30 which is the following:

constructor(title="", text="", completed=false) {....................

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

You are correct, it is not required. Must have left in the second param on accident. Good catch!

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Hey compile,

Is this in JSBin with the ES6 flag switched on? If not, then you need to make sure ES6 is enabled through Babel or some other transpiler.

compile
compile
~ 8 years ago

Was running in local dev environment and had to setup Babel 6, etc to get passing tests. Done now.

Tobias
Tobias
~ 8 years ago

Annoyed with all the extra wrapping: there's a class and every API call wrapped in a function. That's just complicating

Case Taintor
Case Taintor
~ 8 years ago
function updateTodo(todos, todo) {
  return todos.update(todo.id, todo => todo);
}

This does not update the todo since todo => todo will always map the object to itself. The todo from the function definition is masked by the todo in the updater definition.

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Hi Case,

It will update the key in the Todos Map and sets the key to the new todo object. You are correct, it does not update the todo itself.

Kemal Tatar
Kemal Tatar
~ 8 years ago

Without use update function this test passes either

it('should update todo', () => {

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

let 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");

});

So add method gets the reference of todo object. But i didn't get why should i use update method and how. What is the best practice of updating it?

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Hi Kemal,

A slight oversight on my part. todo.title = "New Title" should be a new Todo() with the same ID. Then the original todo won't be mutated, the test will fail unless the updateTodo method is used with the new Todo(). Make sense?

Kemal Tatar
Kemal Tatar
~ 8 years ago

it('should update todo', () => {

const todo = new Todo("Todo 1", "I'm a todo!", false);
let todoId = todo.getId();
console.log(todoId)


const newTodo = new Todo("Hey There","I'm a todo!",false);
newTodo.setId(todoId);


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


todos = updateTodo(todos, newTodo); 

expect(todos.get(todoId).title).to.equal("Hey There");

});

I tried this but it didn't worked either. Weird part is test passes with todos = addTodo(todos, newTodo); function.

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Here's a simpler example that should clarify how to update: https://jsbin.com/bewaku/edit?html,js,output

describe('Modifying an Immutable.js Map()', () => {
  
  it('should update todo', () => {

    const todoID = "random_string_dklsfj9023";
    const todo = new Todo("Todo 1", "I'm a todo!");
    
    let todos = Immutable.Map();
    todos = todos.set(todoID, todo);

    todos = todos.update(todoID, () => new Todo("New Title", "I'm a todo!")); 
    expect(todos.get(todoID).title).to.equal("New Title");
    
  });  
  
});
Kemal Tatar
Kemal Tatar
~ 8 years ago

But in documentation of immutable.js update example is like this :

update(key: K, updater: (value: V) => V): Map<K, V>

here in the updater function we are passing value as parameter but if we do this it does not working. Am i missing something?

This is from source of map update function :

https://github.com/facebook/immutable-js/blob/master/src/Map.js

function updateInDeepMap(existing, keyPathIter, notSetValue, updater) { var isNotSet = existing === NOT_SET; var step = keyPathIter.next(); if (step.done) { var existingValue = isNotSet ? notSetValue : existing; ////////////////////////////////////////////////////////////////////////////////////////////////////////////// var newValue = updater(existingValue); ///////////////////////////////////////////////////////////////////////////////////////////////////////////// return newValue === existingValue ? existing : newValue; } invariant( isNotSet || (existing && existing.set), 'invalid keyPath' ); var key = step.value; var nextExisting = isNotSet ? NOT_SET : existing.get(key, NOT_SET); var nextUpdated = updateInDeepMap( nextExisting, keyPathIter, notSetValue, updater ); return nextUpdated === nextExisting ? existing : nextUpdated === NOT_SET ? existing.remove(key) : (isNotSet ? emptyMap() : existing).set(key, nextUpdated); }

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Kemal, Not sure I understand. update() takes a function that return the updated value. That's all. What isn't working?

Kemal Tatar
Kemal Tatar
~ 8 years ago

update(key: K, updater: (value: V) => V): Map<K, V> Yes but accourding to documentation i should pass the value to updater function but if i do that function is not updating map. https://jsbin.com/ribihun/edit?html,js,output

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Ah, I see why you're confused. :) (newTodo) => newTodo is a function signature, so newTodo is the argument name, not the lexically scoped variable from above. I can see why you thought you were passing the variable to the closure, but you are just returning the original variable from the Map(). In my example, I return a completely new Todo, which is why it updates. Does that make sense?

David Moody
David Moody
~ 8 years ago

I love the wrapping of the immutable functions. this helps me to get an idea of how people use immutable in the wild Thanks do that dude :-)

J.S. Leonard
J.S. Leonardinstructor
~ 8 years ago

Glad to hear it David!

David Moody
David Moody
~ 8 years ago

are there any advantages or disadvantages to having a Map of objects like you are doing here rather than a Map of Maps?

Seb
Seb
~ 8 years ago

I can see why some people find the wrapping of functions strange but it does mean you can look at all methods in one place...

When I look at the bin there still is a second parameter to the delete function, which isn't needed, as mentioned in another comment...

I really like the videos and that you are "typing live". Looking forward to the remainder of the course.

HK
HK
~ 7 years ago

very confusing...

Paweł Waszczyński
Paweł Waszczyński
~ 5 years ago

Very confusing. Just looking at updateTodo example with some many "bugs", it does not encourage to move forward with this course.

Markdown supported.
Become a member to join the discussionEnroll Today