This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Modifying an Immutable.js Map()

5:22 JavaScript lesson by

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.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

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.

Avatar
Nils

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.

Avatar
Nils

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 ?

Avatar
J.S.

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.

In reply to Nils
Avatar
J.S.

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?

In reply to Nils
Avatar
JMBattista

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.

Avatar
J.S.

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!

In reply to JMBattista
Avatar
Sandeep

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

Avatar
compile

Hi
get error in chrome console:

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

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

Avatar
J.S.

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

In reply to Sandeep
Avatar
J.S.

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.

In reply to compile
Avatar
compile

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

In reply to J.S.
Avatar
Tobias

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

Avatar
Case Taintor
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.

Avatar
J.S.

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.

In reply to Case Taintor
Avatar
Kemal

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?

Avatar
J.S.

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?

In reply to Kemal
Avatar
Kemal

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.

In reply to J.S.
Avatar
J.S.

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

  });  

});
In reply to Kemal
Avatar
Kemal

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

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

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 === NOTSET;
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, NOTSET);
var nextUpdated = updateInDeepMap(
nextExisting,
keyPathIter,
notSetValue,
updater
);
return nextUpdated === nextExisting ? existing :
nextUpdated === NOT
SET ? existing.remove(key) :
(isNotSet ? emptyMap() : existing).set(key, nextUpdated);
}

In reply to J.S.
Avatar
J.S.

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

In reply to Kemal
Avatar
Kemal

update(key: K, updater: (value: V) => V): Map
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

In reply to J.S.
Avatar
J.S.

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?

In reply to Kemal
Avatar
David Moody

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 :-)

Avatar
J.S.

Glad to hear it David!

In reply to David Moody
Avatar
David Moody

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

Avatar
Seb

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.

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.

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.

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

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?