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.

ffxsam
  • 26,428
  • 32
  • 94
  • 144
  • 1
    `state.update('things', things => things.filter(thing => thing.get('id') !== 'a2'));` – zerkms Apr 23 '16 at 10:51
  • 1
    Does this answer your question? [Delete object from ImmutableJS List based upon property value](https://stackoverflow.com/questions/31466400/delete-object-from-immutablejs-list-based-upon-property-value) – JBallin Nov 18 '20 at 03:00

6 Answers6

42

You can use Array#filter.

return state.set('things', state.get('things').filter(o => o.get('id') !== 'a1'));
Tushar
  • 85,780
  • 21
  • 159
  • 179
  • So simple - I was apparently trying too hard to use Immutable's methods :) Thanks! – ffxsam Apr 21 '16 at 06:20
  • 2
    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. – hazardous Apr 23 '16 at 10:37
  • i am getting o.get is not a function... any help!! – user2906608 May 18 '17 at 12:46
  • 2
    I LOOOOOOOOVE this!!! way easier than looping through and splicing , never again shall I do that. – King Friday Sep 27 '17 at 17:36
  • 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')); – Oliver Watkins Nov 12 '17 at 14:31
  • it worked for me but I needed o.id !== 'a1' and not o.get('id') !== 'a1' – Nir O. Jan 10 '18 at 10:18
  • This is the better answer when you don't have nested arrays. I came back here looking again for an answer but had to come up with one for deeply nested arrays but if you have something for that case as well, by all means. – King Friday Jun 12 '18 at 15:56
  • 1) Why not use update instead of set? 2) Why would you loop through the entire array to remove one value? – JBallin Nov 18 '20 at 03:03
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)];
Musa
  • 2,596
  • 26
  • 25
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.

hazardous
  • 10,627
  • 2
  • 40
  • 52
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?

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
}
pravdomil
  • 2,961
  • 1
  • 24
  • 38
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.

King Friday
  • 25,132
  • 12
  • 90
  • 84