196

I am using Redux. In my reducer I'm trying to remove a property from an object like this:

const state = {
    a: '1',
    b: '2',
    c: {
       x: '42',
       y: '43'
    },
}

And I want to have something like this without having to mutate the original state:

const newState = {
    a: '1',
    b: '2',
    c: {
       x: '42',
    },
}

I tried:

let newState = Object.assign({}, state);
delete newState.c.y

but for some reasons, it deletes the property from both states.

Could help me to do that?

Aᴍɪʀ
  • 7,623
  • 3
  • 38
  • 52
Vincent Taing
  • 3,283
  • 2
  • 18
  • 24
  • 3
    Note that `Object.assign` creates only a [shallow copy](https://en.wikipedia.org/wiki/Object_copying#Shallow_copy) of `state` and therefore `state.c` and `newState.c` will point to the same shared object. You tried to delete property `y` from the shared object `c` and not from the new object `newState`. – Akseli Palén Nov 12 '18 at 18:30

17 Answers17

317

How about using destructuring assignment syntax?

const original = {
  foo: 'bar',
  stack: 'overflow',
};

// If the name of the property to remove is constant
const { stack, ...withoutFirst } = original;
console.log(withoutFirst); // Will be { "foo": "bar" }

// If the name of the property to remove is from a variable
const key = 'stack'
const { [key]: value, ...withoutSecond } = original;
console.log(withoutSecond); // Will be { "foo": "bar" }

// To do a deep removal with property names from variables
const deep = {
  foo: 'bar',
  c: {
   x: 1,
   y: 2
  }
};

const parentKey = 'c';
const childKey = 'y';
// Remove the 'c' element from original
const { [parentKey]: parentValue, ...noChild } = deep;
// Remove the 'y' from the 'c' element
const { [childKey]: removedValue, ...childWithout } = parentValue;
// Merge back together
const withoutThird = { ...noChild, [parentKey]: childWithout };
console.log(withoutThird); // Will be { "foo": "bar", "c": { "x": 1 } }
madebydavid
  • 6,457
  • 2
  • 21
  • 29
  • 3
    wonderful,without extra library,make use of es6 and simple enough. – sbk201 Dec 28 '17 at 12:06
  • 21
    Nice! Here's an es6 helper function to go along with it `const deleteProperty = ({[key]: _, ...newObj}, key) => newObj;`. Usage: `deleteProperty({a:1, b:2}, "a");` gives `{b:2}` – mikebridge Jan 18 '18 at 18:01
  • 1
    Note that the deep removal example will crash if `deep['c']` is empty, so in a general case, you might want to add a check for the presence of the key. – Laurent S Jun 04 '19 at 09:04
  • In the first example you would be left over with an unused variable `stack`. – Raul Špilev Mar 18 '20 at 10:41
  • plugged this utility function right into my reducer function removeProperty(original, property) { let { [property]: _, ...newObj } = original; return newObj } – Meursault Jul 11 '20 at 01:40
  • i made an small function to remove a key from a no deep object on typescript ` function omitObjectKey(key: keyof T, object: T): T { const { [key]: value, ...objectWithoutkey } = object; return objectWithoutkey as T }` hope it helps :3 – Cristian G Jan 22 '21 at 03:32
50

I find ES5 array methods like filter, map and reduce useful because they always return new arrays or objects. In this case I'd use Object.keys to iterate over the object, and Array#reduce to turn it back into an object.

return Object.assign({}, state, {
    c: Object.keys(state.c).reduce((result, key) => {
        if (key !== 'y') {
            result[key] = state.c[key];
        }
        return result;
    }, {})
});
David L. Walsh
  • 24,097
  • 10
  • 61
  • 46
  • chiming in with my slight change to make it clearer IMO... also lets you omit multiple properties. const omit = ['prop1', 'prop2'] ... if (omit.indexOf(key) === -1) result[key] = state.c[key] return result; ... – josh-sachs May 05 '17 at 19:33
  • 4
    ES6 equivalent to get a copy of `myObject` with key `myKey` removed: `Object.keys(myObject).reduce((acc, cur) => cur === myKey ? acc : {...acc, [cur]: myObject[cur]}, {})` – Sang Feb 27 '18 at 07:52
  • This won't work if the object has inherited properties... – Ruan Mendes Jan 29 '21 at 15:26
  • @JuanMendes React state should just be key-value pairs. Inherited properties are a red herring. – David L. Walsh Feb 02 '21 at 02:42
  • Sure, the question does mention redux... It's just that it's its title doesn't mention anything and it's the top result when looking for ways to remove from an object so it may as well have generic information that's useful to everyone – Ruan Mendes Feb 02 '21 at 15:44
45

You can use _.omit(object, [paths]) from lodash library

path can be nested for example: _.omit(object, ['key1.key2.key3'])

Dmitri
  • 668
  • 4
  • 6
  • 3
    Unfortunately, `_.omit` cannot delete deep properties (what OP was asking). There's [`omit-deep-lodash`](https://www.npmjs.com/package/omit-deep-lodash) module for this purpose. – pumbo Feb 20 '17 at 15:30
  • 2
    Going off what @AlexM was saying. I found it useful, and it might be more appropriate, to us ```_.cloneDeep(obj)``` from lodash. That copies the object easily and then you can simply use js ```delete obj.[key]``` to remove the key. – Alex J Oct 08 '18 at 17:28
  • Agree w/ @AlexJ, cloneDeep works perfectly and you can use the spread syntax with it as well: ..._.cloneDeep(state) for Redux – Sean Chase Oct 09 '18 at 17:50
36

Just use ES6 object destructuring feature

const state = {
    c: {
       x: '42',
       y: '43'
    },
}

const { c: { y, ...c } } = state // generates a new 'c' without 'y'

console.log({...state, c }) // put the new c on a new state
Ramon Diogo
  • 1,630
  • 2
  • 14
  • 9
  • 11
    `const {y, ...c} = state.c` might be a little clearer than having two `c`'s on the left hand side. – dosentmatter Mar 13 '18 at 20:14
  • 11
    beware: this doesn't work for an object keyed by integers – daviestar Apr 10 '18 at 13:36
  • 3
    From the below answer, if you need to reference the variable name to be deleted: `const name = 'c'` then you can do `const {[name]:deletedValue, ...newState} = state` then return `newState` in your reducer. This is for a top level key deletion – FFF Apr 16 '18 at 20:53
24

That's because you are copying the value of state.c to the other object. And that value is a pointer to another javascript object. So, both of those pointers are pointing to the same object.

Try this:

let newState = Object.assign({}, state);
console.log(newState == state); // false
console.log(newState.c == state.c); // true
newState.c = Object.assign({}, state.c);
console.log(newState.c == state.c); // now it is false
delete newState.c.y;

You can also do a deep-copy of the object. See this question and you'll find what's best for you.

Community
  • 1
  • 1
Aᴍɪʀ
  • 7,623
  • 3
  • 38
  • 52
  • 2
    This is an excellent answer! `state.c` is a reference, and the reference is being copied just fine. Redux wants a normalized state shape, which means using ids instead of references when nesting state. Check out the redux docs: http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html – Ziggy Oct 15 '17 at 04:29
18

How about this:

function removeByKey (myObj, deleteKey) {
  return Object.keys(myObj)
    .filter(key => key !== deleteKey)
    .reduce((result, current) => {
      result[current] = myObj[current];
      return result;
  }, {});
}

It filters the key that should be deleted then builds a new object from the remaining keys and the initial object. The idea is stolen from Tyler McGinnes awesome reactjs program.

JSBin

SebK
  • 487
  • 4
  • 12
14

As of 2019, another option is to use the Object.fromEntries method. It has reached stage 4.

const newC = Object.fromEntries(
    Object.entries(state.c).filter(([key]) => key != 'y')
)
const newState = {...state, c: newC}

The nice thing about it is that it handles integer keys nicely.

jian
  • 1,236
  • 11
  • 11
13
function dissoc(key, obj) {
  let copy = Object.assign({}, obj)
  delete copy[key]
  return copy
}

Also, if looking for a functional programming toolkit, look at Ramda.

Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67
10

You may use Immutability helper in order to unset an attribute, in your case:

import update from 'immutability-helper';

const updatedState = update(state, {
  c: {
    $unset: ['y']
  }
});    
Javier P
  • 1,332
  • 14
  • 21
6

It's easy with Immutable.js:

const newState = state.deleteIn(['c', 'y']);

description of deleteIn()

quotesBro
  • 6,030
  • 2
  • 31
  • 41
6

Here's an easy 1-liner you can use that allows you to partially apply the prop you want to remove. This makes it easy to pass to Array.map.

const removeProp = prop => ({ [prop]: _, ...rest }) => ({ ...rest })

Now you can use it like this:

const newArr = oldArr.map(removeProp('deleteMe'))
Brennan Cheung
  • 4,271
  • 4
  • 30
  • 29
2

The issue you are having is that you are not deep cloning your initial state. So you have a shallow copy.

You could use spread operator

  const newState = { ...state, c: { ...state.c } };
  delete newState.c.y

Or following your same code

let newState = Object.assign({}, state, { c: Object.assign({}, state.c) });
delete newState.c.y
Juan Carrey
  • 696
  • 1
  • 6
  • 13
1

I normally use

Object.assign({}, existingState, {propToRemove: undefined})

I realise this isn't actually removing the property but for almost all purposes 1 its functionally equivalent. The syntax for this is much simpler than the alternatives which I feel is a pretty good tradeoff.

1 If you are using hasOwnProperty(), you will need to use the more complicated solution.

undefined
  • 33,537
  • 22
  • 129
  • 198
1

I use this pattern

const newState = Object.assign({}, state);
      delete newState.show;
      return newState;

but in book i saw another pattern

return Object.assign({}, state, { name: undefined } )
zloctb
  • 10,592
  • 8
  • 70
  • 89
1

utility ;))

const removeObjectField = (obj, field) => {

    // delete filter[selectName]; -> this mutates.
    const { [field]: remove, ...rest } = obj;

    return rest;
}

action type

const MY_Y_REMOVE = 'MY_Y_REMOVE';

action creator

const myYRemoveAction = (c, y) => {

    const result = removeObjectField(c, y);

        return dispatch =>
            dispatch({
                type: MY_Y_REMOVE,
                payload: result
            })
    }

reducer

export default (state ={}, action) => {
  switch (action.type) {
    case myActions.MY_Y_REMOVE || :
      return { ...state, c: action.payload };
    default:
      return state;
  }
};
Musa
  • 2,596
  • 26
  • 25
0

As hinted in some of the answers already, it's because you are trying to modify a nested state ie. one level deeper. A canonical solution would be to add a reducer on the x state level:

const state = {
    a: '1',
    b: '2',
    c: {
       x: '42',
       y: '43'
    },
}

Deeper level reducer

let newDeepState = Object.assign({}, state.c);
delete newDeepState.y;

Original level reducer

let newState = Object.assign({}, state, {c: newDeepState});
Mieszko
  • 330
  • 2
  • 6
-1

Use a combination of Object.assign, JSON.parse and JSON.stringify

const obj1 = { a: "a", b: "b" };
const obj2 = { c: "c", a: undefined };

const merged = Object.assign({}, obj1, obj2);

const sanitized = JSON.parse(JSON.stringify(merged));

console.log(sanitized); // -> { b: "b", c: "c" }
Aleem Isiaka
  • 119
  • 1
  • 7