26

Given a state like this:

state = { things: [ { id: 'a1', name: 'thing 1' }, { id: 'a2', name: 'thing 2' }, ], }; 

How can I create a new state where ID "a1" is removed? It's easy enough to push new items:

return state.set(state.get('things').push(newThing)); 

But I can't figure out how to search for and remove an object by its id property. I tried this:

return state.set('tracks', state.get('tracks').delete( state.get('tracks').findIndex(x => x.get('id') === 'a2') ) ) 

But it seems messy, plus it only works if the item is found, because if findIndex returns -1, that's a valid value for delete.

2

6 Answers 6

42

You can use Array#filter.

return state.set('things', state.get('things').filter(o => o.get('id') !== 'a1')); 
Sign up to request clarification or add additional context in comments.

8 Comments

So simple - I was apparently trying too hard to use Immutable's methods :) Thanks!
There's one caveat, this would always return a new immutable, even when the array has no matching elements. Depending on the size of the structure, this may be something worth knowing as it will effectively defeat any rendering optimizations done based on immutable state comparisons in React.
i am getting o.get is not a function... any help!!
I LOOOOOOOOVE this!!! way easier than looping through and splicing , never again shall I do that.
I think this is wrong. Shouldn't it be setIn and getIn. ie. state.setIn('things', state.getIn('things').filter(o => o.get('id') !== 'a1'));
|
15

When you are using filter it iterates all cycle -> one effective way is finding index => slice and using splitter ...

const index = state.findIndex(data => data.id === action.id); return [...state.slice(0, index), ...state.slice(index + 1)]; 

Comments

5

Alternatively, as you are "searching and then deleting"...

var itemIndex = this.state.get("tracks").findIndex(x => x.get('id') === 'a2'); return itemIndex > -1 ? this.state.deleteIn(["tracks", itemIndex]) : this.state; 

This will ensure the state is not mutated when there are no changes.

Comments

1

Found this thread while looking for a solution to a similar task. Solved it with update method:

return state.update('things', (things) => things.filter((t) => t.id !== action.things.id)) 

any idea/comment which one is better/preferred?

Comments

0

You can do that even without immutable.js with following function.

function arrayFilter(array, filter) { let ret = array let removed = 0 for (let index = 0; index < array.length; index++) { const passed = filter(array[index], index, array) if (!passed) { ret = [...ret.slice(0, index - removed), ...ret.slice(index - removed + 1)] removed++ } } return ret } 

Comments

0

ImmutableJS working with nested arrays

Immutablejs is great but at the same time makes things more complicated in some edge cases, particularly when working with nested arrays.

Sometimes it is easier to take it back to JS in a general sense for this particular issue.

// 1. get a copy of the list into normal JavaScript const myList = state.getIn(['root', 'someMap', 'myList']).toJS() // 2. remove item in list using normal JavaScript and/or anything else myList.splice(deleteIndex, 1) // 3. return the new state based on mutated myList return state .mergeDeep({ root: { someMap: { myList: undefined } }}) .mergeDeep({ root: { someMap: { myList } }}) 

Unfortunately, step 3 is necessary to specifically set to undefined because if you simply set myList directly as an array value, ImmutableJS will do a comparison of values between the current list and only modify them creating strange behavior.

The justification for this is to simplify the mental overhead. I do not recommend doing this in a loop, rather manipulate the pure JS array in a loop if you must but should be prior to step 3.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.