Remove a property in an object immutably
Asked Answered
S

17

198

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?

Simonne answered 21/12, 2015 at 17:18 Comment(1)
Note that Object.assign creates only a 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.Uncork
A
320

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 } }
Allfired answered 10/11, 2017 at 16:27 Comment(6)
wonderful,without extra library,make use of es6 and simple enough.Biretta
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}Kiersten
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.Chinn
In the first example you would be left over with an unused variable stack.Electrojet
plugged this utility function right into my reducer function removeProperty(original, property) { let { [property]: _, ...newObj } = original; return newObj }Dido
i made an small function to remove a key from a no deep object on typescript ` function omitObjectKey<T>(key: keyof T, object: T): T { const { [key]: value, ...objectWithoutkey } = object; return objectWithoutkey as T }` hope it helps :3Jung
V
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;
    }, {})
});
Viburnum answered 23/12, 2015 at 6:31 Comment(5)
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; ...Dutton
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]}, {})Demur
This won't work if the object has inherited properties...Sankhya
@JuanMendes React state should just be key-value pairs. Inherited properties are a red herring.Viburnum
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 everyoneSankhya
A
45

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

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

Azarcon answered 24/12, 2015 at 7:0 Comment(3)
Unfortunately, _.omit cannot delete deep properties (what OP was asking). There's omit-deep-lodash module for this purpose.Hand
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.Beet
Agree w/ @AlexJ, cloneDeep works perfectly and you can use the spread syntax with it as well: ..._.cloneDeep(state) for ReduxSophiasophie
T
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
Turnspit answered 21/2, 2018 at 12:31 Comment(3)
const {y, ...c} = state.c might be a little clearer than having two c's on the left hand side.Schlessel
beware: this doesn't work for an object keyed by integersPickaxe
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 deletionIreneirenic
D
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.

Distraction answered 21/12, 2015 at 17:22 Comment(1)
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: redux.js.org/docs/recipes/reducers/NormalizingStateShape.htmlHeyes
I
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

Incalescent answered 16/5, 2016 at 6:2 Comment(0)
P
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.

Plummy answered 20/4, 2019 at 4:6 Comment(0)
A
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.

Afrikah answered 3/7, 2017 at 19:6 Comment(0)
C
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']
  }
});    
Copyedit answered 17/8, 2017 at 17:48 Comment(2)
Then how to delete all the property "y" of every array object item?Ceja
@5ervant I think that's another question, but I'd suggest you to map the array and apply any of the given solutions hereCopyedit
P
6

It's easy with Immutable.js:

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

description of deleteIn()

Papp answered 30/9, 2016 at 11:21 Comment(0)
R
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'))
Rozalin answered 24/11, 2020 at 2:44 Comment(0)
D
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
Diminutive answered 24/5, 2018 at 12:25 Comment(0)
P
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.

Preindicate answered 16/11, 2017 at 22:39 Comment(0)
W
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 } )
Whisenhunt answered 19/1, 2018 at 7:11 Comment(1)
The second one, does not remove the key. It just sets it to undefined.Pikeperch
H
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;
  }
};
Huldahuldah answered 8/3, 2018 at 21:1 Comment(0)
V
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});
Vazquez answered 6/12, 2017 at 11:30 Comment(0)
W
-2

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" }
Wickham answered 13/1, 2022 at 8:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.