How to update element inside List with ImmutableJS?
The most appropriate case is to use both findIndex
and update
methods.
list = list.update(
list.findIndex(function(item) {
return item.get("name") === "third";
}), function(item) {
return item.set("count", 4);
}
);
P.S. It's not always possible to use Maps. E.g. if names are not unique and I want to update all items with the same names.
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
Explanation
Updating Immutable.js collections always return new versions of those collections leaving the original unchanged. Because of that, we can't use JavaScript's list[2].count = 4
mutation syntax. Instead we need to call methods, much like we might do with Java collection classes.
Let's start with a simpler example: just the counts in a list.
var arr = [];
arr.push(2);
arr.push(1);
arr.push(2);
arr.push(1);
var counts = Immutable.List.of(arr);
Now if we wanted to update the 3rd item, a plain JS array might look like: counts[2] = 4
. Since we can't use mutation, and need to call a method, instead we can use: counts.set(2, 4)
- that means set the value 4
at the index 2
.
Deep updates
The example you gave has nested data though. We can't just use set()
on the initial collection.
Immutable.js collections have a family of methods with names ending with "In" which allow you to make deeper changes in a nested set. Most common updating methods have a related "In" method. For example for set
there is setIn
. Instead of accepting an index or a key as the first argument, these "In" methods accept a "key path". The key path is an array of indexes or keys that illustrates how to get to the value you wish to update.
In your example, you wanted to update the item in the list at index 2, and then the value at the key "count" within that item. So the key path would be [2, "count"]
. The second parameter to the setIn
method works just like set
, it's the new value we want to put there, so:
list = list.setIn([2, "count"], 4)
Finding the right key path
Going one step further, you actually said you wanted to update the item where the name is "three" which is different than just the 3rd item. For example, maybe your list is not sorted, or perhaps there the item named "two" was removed earlier? That means first we need to make sure we actually know the correct key path! For this we can use the findIndex()
method (which, by the way, works almost exactly like Array#findIndex).
Once we've found the index in the list which has the item we want to update, we can provide the key path to the value we wish to update:
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)
NB: Set
vs Update
The original question mentions the update methods rather than the set methods. I'll explain the second argument in that function (called updater
), since it's different from set()
. While the second argument to set()
is the new value we want, the second argument to update()
is a function which accepts the previous value and returns the new value we want. Then, updateIn()
is the "In" variation of update()
which accepts a key path.
Say for example we wanted a variation of your example that didn't just set the count to 4
, but instead incremented the existing count, we could provide a function which adds one to the existing value:
var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)
With .setIn() you can do the same:
let obj = fromJS({
elem: [
{id: 1, name: "first", count: 2},
{id: 2, name: "second", count: 1},
{id: 3, name: "third", count: 2},
{id: 4, name: "fourth", count: 1}
]
});
obj = obj.setIn(['elem', 3, 'count'], 4);
If we don’t know the index of the entry we want to update. It’s pretty easy to find it using .findIndex():
const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);
Hope it helps!
Here is what official docs said…
updateIn
You don't need updateIn
, which is for nested structures only. You are looking for the update
method, which has a much simpler signature and documentation:
Returns a new List with an updated value at index with the return value of calling updater with the existing value, or notSetValue if index was not set.
update(index: number, updater: (value: T) => T): List<T> update(index: number, notSetValue: T, updater: (value: T) => T): List<T>
which, as the Map::update
docs suggest, is "equivalent to: list.set(index, updater(list.get(index, notSetValue)))
".
where element with name "third"
That's not how lists work. You have to know the index of the element that you want to update, or you have to search for it.
How can I update list where element with name third have its count set to 4?
This should do it:
list = list.update(2, function(v) {
return {id: v.id, name: v.name, count: 4};
});