14

Is there any elegant way of removing an object from an array which is part of an array? I have been working with React and Redux for a while now but get stuck several hours everytime I have to remove or insert data without mutating the state.

The reducer is an array containing objects which have an ID and another array with objects, like this:

[
 { id:123,
   items:[
           { id: abc,
             name: albert
           }, 
           ... 
         ]
 }, 
 ... 
]

I receive both IDs and need to remove the item with ID abc.

Erwin Mayer
  • 18,076
  • 9
  • 88
  • 126
Daniel Storch
  • 979
  • 3
  • 10
  • 25
  • 1
    You should read this one and reconsider your state structure. http://stackoverflow.com/questions/32135779/updating-nested-data-in-redux-store/32921731#32921731 – larrydahooster Feb 11 '16 at 15:58
  • Instead of passing the full complex array to the reducer, create a new reducer that receive's the item of the array as the state to make the change – Roger Barreto Feb 11 '16 at 18:21
  • Well, thats also a way of doing it. But than every time i need the items of the object with id:123, i have to "search" for dem. And another question, when i receive the JSON like this from the backend, how do i split it to several reducers? The fetching is done via an action which than goes to one reducer. – Daniel Storch Feb 12 '16 at 09:19

6 Answers6

35

To remove an item from an array by id:

return state.filter(item => item.id !== action.id)

To remove a key from an object by id:

let copy = Object.assign({}, state) // assuming you use Object.assign() polyfill!
delete copy[action.id] // shallowly mutating a shallow copy is fine
return copy

(Bonus) The same with object spread operator proposal:

let { [action.id]: deletedItem, ...rest } = state
return rest
Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
  • A combination of those is what i ended up using. – Daniel Storch Mar 06 '16 at 12:27
  • 2
    What does `deletedItem` do/mean/contain in the last example? – Mbrevda Aug 14 '16 at 13:53
  • 2
    @Mbrevda `deletedItem` is just a variable name that's assigned the item that was deleted (the item assigned to `action.id`). You're turning your state into two new objects, one that has the rejected item, the other with the remaining items. – Matt McDonald Nov 04 '16 at 09:43
  • @MattMcDonald ah, makes sense! Which there was a syntax for ignoring/deleting while destructuring. Something like: `{!foo, !bar, ...rest}` – Mbrevda Nov 04 '16 at 09:45
  • 4
    If you're using rest-spread, it currently does not work with numeric keys. In that case you have to use `[action.id.toString()]`. See the issue https://github.com/babel/babel/issues/4196, and the proposed fix https://github.com/babel/babel/pull/3675 – well Apr 11 '17 at 01:19
  • 1
    so elegant, I love {deletedItem,...rest} idea – Hiep Tran Nov 29 '17 at 08:46
2
const remove = (state, bucketId, personId) => state.map(
  bucket => bucket.id === bucketId
    ? { ...bucket, items: bucket.items.filter(person => person.id !== personId) }
    : bucket,
);

Usage:

const state = [
  {
    id: 123,
    items: [
      {
        id: 'abc',
        name: 'Kinna',
      },
      {
        id: 'def',
        name: 'Meggy',
      },
    ],
  },
  {
    id: 456,
    items: [
      {
        id: 'ghi',
        name: 'Ade',
      },
      {
        id: 'jkl',
        name: 'Tades',
      },
    ],
  },
];

console.log(remove(state, 123, 'abc'));
glennsl
  • 28,186
  • 12
  • 57
  • 75
0

You can use Underscore's reject. It does exactly what you're trying to do.

Cool Acid
  • 153
  • 1
  • 9
0

If you decide for plain Javascript, the most elegant way I can think of is to use Array.prototype.reduce to reduce the state:

var state = [
 { id: 123,
   items:[
           { id: 'abc',
             name: 'albert'
           }, 
           ... 
         ]
 }, 
 ... 
]

function filterOut (state) {
  return (bucketId, personId) => {
    return state.reduce((state, bucket) => {
      return state.concat(
        (bucketId === bucket.id) ?
          Object.assign({}, bucket, {items: bucket.items.filter((person) => person.id !== personId)}) :
          bucket
      );
    }, []);
  }
}

var newState = filterOut(state)(123, 'abc');
danielepolencic
  • 4,905
  • 1
  • 26
  • 25
0

You can also use lodash's omit method.

Be aware that importing lodash will increase your build size quite a bit. Keep it in check somewhat by importing just the specific method: import omit from 'lodash/omit';

If possible, I suggest using the object spread operator as described in Dan's answer.

Community
  • 1
  • 1
0

i solved my issue in this way

if(action.type === "REMOVE_FROM_PLAYLIST"){
        let copy = Object.assign({}, state) 
        delete copy.playlist[action.index].songs[action.indexSongs];

        return copy;
    }

hope it helps any one else.

Dante Cervantes
  • 323
  • 1
  • 3
  • 14