ffxsam
ffxsam

Reputation: 27713

How to remove an object from an array in Immutable?

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.

Upvotes: 26

Views: 32412

Answers (6)

King Friday
King Friday

Reputation: 26076

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.

Upvotes: 0

pravdomil
pravdomil

Reputation: 2969

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
}

Upvotes: 0

Musa
Musa

Reputation: 2662

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

Upvotes: 15

Roman Krayovskyy
Roman Krayovskyy

Reputation: 111

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?

Upvotes: 1

hazardous
hazardous

Reputation: 10837

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.

Upvotes: 5

Tushar
Tushar

Reputation: 87203

You can use Array#filter.

return state.set('things', state.get('things').filter(o => o.get('id') !== 'a1'));

Upvotes: 42

Related Questions