Redux state is being mutated with lodash's mergeWith - javascript

I am using lodash's mergeWith to merge some payload data into some of my redux state. However, when doing this, I end up directly mutating state. I don't understand how this is happening, since I am using {...state} to make the merge occur. Why is this happening and what can I do to not mutate my state directly? You can see the below snippet for an example of what is happening. Thanks!
const merger = (objectOne, objectTwo) => {
const customizer = (firstValue, secondValue) => {
return _.isArray(firstValue) ? secondValue : undefined;
};
return _.mergeWith(objectOne, objectTwo, customizer);
};
const state = {
1: {a: true, b: true, c: true},
2: {a: true, b: true, c: true},
3: {a: true, b: true, c: true},
}
const payload = {
2: {a: true, b: false, c: true},
}
console.log("Merged data:");
console.log(merger({...state}, payload));
console.log("Manipulated state:");
console.log(state);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.10/lodash.min.js"></script>

Here is in a nut shell the issue:
let a = { foo: 'A'}
let c = { ... a } // shallow copy of a
a.foo = 'boo'
console.log(a)
console.log(c) // works as expected c.foo is NOT changed and still is 'A'
As you can see from the above example with spreading and value based properties shallow copy works as expected. However when you do this:
let x = { foo: { boo: 'A' }} // object as value this time
let y = { ... x } // shallow copy of x
x.foo.boo = 'beer'
console.log(x.foo.boo)
console.log(y.foo.boo) // should be 'boo' but it is 'beer'
Shallow copy does not work as well since the clone has references pointing to the old x objects instead of cloned ones.
To remedy this and also to to make your code somewhat more concise you could:
const state = { 1: {a: true, b: true, c: true}, 2: {a: true, b: true, c: true}, 3: {a: true, b: true, c: true}, }
const payload = { 2: {a: true, b: false, c: true} }
const merger = (...args) => _.mergeWith(...args, (a,b) => _.isArray(a) ? b : undefined)
console.log("Merged data:");
console.log(merger(_.cloneDeep(state), payload));
console.log("Manipulated state:");
console.log(state);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
First switch ot lodash _.cloneDeep which would deep copy your entire object tree and also you can make your merge method more concise with ES6 spread etc.

You have to be aware that the spread syntax { ...state } does only a shallow copy of your object. So in a fact - the deeply nested properties - in your particular case { a: true, b: true, c: true } remain the same objects by reference. If you wish to avoid the state mutation error, you should use e.g. cloneDeep function from lodash.
merger(_.cloneDeep(state), payload);

Related

Compare response in jest unit test case

I'm new to jest unit test case scenario, I have a scenario where in the response from the service that I called is of the below format
Artifact {
name: 'detection-v1.zip',
file_path: 'artifact\\bn-ds-anomalydetection-v1.zip',
is_tenant: false,
metadata: [
Registerfact {
name: 'ad',
_meta: [Object],
line_meta: [Object]
},
Registerfact {
name: 'ad-generic',
_meta: [Object],
line_meta: [Object]
}
]
}
how can i compare the above response in the jest service , I was trying to create a object but the Artifact name before the object is confusing how should i proceed
The test case is
test('test processArtifact method', async()=>{
const mockGetRestClient = jest.fn();
try{
const response = await factService.processfact(artifact)
console.log("response---",response)
// expect(response).toEqual()
}
catch(e){ }
})
I know its silly question ,but i'm confused hence posted it.
How should i create the static object to be put in .toEqual() ?
You can declare a global/static var with your response object on top of file. Or better declare it in some constants file and import here.
For Comparison:
Usually, if you have a simple object, you can use JSON.stringify. However, it may give error due to different order of object keys.
You should use assert for the deep comparison. There is method assert.deepEqual() which does deep comparison of objects.
an example for using assert from official docs
import assert from 'node:assert';
const obj1 = {
a: {
b: 1
}
};
const obj2 = {
a: {
b: 2
}
};
const obj3 = {
a: {
b: 1
}
};
const obj4 = Object.create(obj1);
assert.deepEqual(obj1, obj1);
// OK
// Values of b are different:
assert.deepEqual(obj1, obj2);
// AssertionError: { a: { b: 1 } } deepEqual { a: { b: 2 } }
assert.deepEqual(obj1, obj3);
// OK
// Prototypes are ignored:
assert.deepEqual(obj1, obj4);
// AssertionError: { a: { b: 1 } } deepEqual {}
Hope this helps, let me know if you have any questions.
You can use JSON.stringify in order to convert your object into a string and then compare this result to the one you expect.
console.log(JSON.stringify({
name: 'detection-v1.zip',
file_path: 'artifact\\bn-ds-anomalydetection-v1.zip',
is_tenant: false,
metadata: [
{Registerfact: {
name: 'ad',
_meta: {},
line_meta: {}
}},
{Registerfact: {
name: 'ad-generic',
_meta: {},
line_meta: {}
}}
]
}));

How to use a destructuring assignment inside an object

Is it possible to use a destructuring assignment inside an object?
This works
const test = {a: 'hey', b: 'hello'}
const {a,b} = test;
const destruct = {
a,
b
};
Would like to do this
const test = {a: 'hey', b: 'hello'}
// something like this
const destruct = {
{a,b}: test
};
const destruct = {
{a}: test,
{b}: test
};
If I understand correctly, it seems the spread syntax is a good fit for what you need.
The spread syntax "..." allows you to "spread" the key/value pairs from a source object (ie test) to a target object (ie destruct):
const test = {
a: 'hey',
b: 'hello',
c: 'goodbye'
}
const destruct = {
// {a,b}: test <-- invalid syntax
...test // equivalent using the "spread" syntax
};
console.log(destruct)
Additionally, if you wanted to select a subset of keys from a source object and spread those into a target object then this can be achieved by the following:
const test = {
a: 'hey',
b: 'hello',
c: 'goodbye'
}
/* Spread subset of keys from source object to target object */
const welcomeOnly = {
...({ a, b } = test, { a, b })
}
console.log('exclude goodbye, show welcomes only:', welcomeOnly);
The second example works by destructing the source object (ie test) into an object, with the subset of keys that we want (a and b).
In the scope of that expression (ie everything between the ( and )), these keys are accessible as local variables. We take advantage of this, and pass those to a new object (ie { a, b }). Because the new object is declared after the ,, it is returned as the result of the expression.
If you are trying to take a subset of properties you can use the rest operator
const test = {
a: 'hey',
b: 'hello',
c: 'goodbye'
};
const { c, ...destruct } = test;
console.log(destruct);
This assigns c to a const and the the left over properties are assigned to the const destruct. List all the unwanted properties first and then the left over properties are caught with the rest operator.
Works with arrays as well.
const test = ['hey', 'hello', 'goodbye'];
const [ first, ...rest ] = test;
console.log(rest);
You can try to work like this for destructuring arrays!
let abc = {
a: 'hello',
b: 'hey',
c: 'hi, there!'
}
let {a: x, b:y, c:z} = abc;
console.log(x,y,z)
// "hello"
"hey"
"hi, there!"

Remove a property in an object immutably

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?
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 } }
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;
}, {})
});
You can use _.omit(object, [paths]) from lodash library
path can be nested for example: _.omit(object, ['key1.key2.key3'])
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
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.
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
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.
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.
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']
}
});
It's easy with Immutable.js:
const newState = state.deleteIn(['c', 'y']);
description of deleteIn()
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'))
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
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.
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 } )
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;
}
};
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});
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" }

Javascript compare objects having functions using lodash isEqual

how to compare two objects for equality if they have functions? lodash's isEqual works really well until functions are thrown in:
_.isEqual({
a: 1,
b: 2
}, {
b: 2,
a: 1
});
// -> true
_.isEqual({
a: 1,
b: 2,
c: function () {
return 1;
}
}, {
a: 1,
b: 2,
c: function () {
return 1;
}
});
// -> false
This is what I tried:
_.isEqual(o1, o2, function(val1, val2) {
if(_.isFunction(val1) && _.isFunction(val2)) {
return val1.toString() === val2.toString();
}
})
Lodash supports a customizer function which allows you to write your own equality checks. This seems to be a good enough test to see if the functions are character by character the same.
Are you sure you want to compare functions? If you only care about comparing every property that isn't a function, this is easy to do with lodash:
var o1 = { a: 1, b: 2, c: function() { return 1; } },
o2 = { a: 1, b: 2, c: function() { return 1; } };
_.isEqual(o1, o2)
// → false
_.isEqual(_.omit(o1, _.functions(o1)), _.omit(o2, _.functions(o2)));
// → true
The functions() function returns a list of function properties, and using omit(), you can get rid of them.
Try isEqualWith instead:
import { isEqualWith, isFunction } from 'lodash-es'
const o1 = { fn() {} }
const o2 = { fn() {} }
const equal = isEqualWith(o1, o2, (v1, v2) =>
// if `customizer` returns `undefined`, comparisons are handled by the method instead
isFunction(v1) && isFunction(v2) ? `${v1}` === `${v2}` : undefined,
)
console.log({ equal }) // { equal: true }
As the lodash documentation states:
Functions and DOM nodes are not supported.
https://lodash.com/docs#isEqual

Filtering object properties based on value

Is there some elegant way of filtering out falsey properties from this object with lodash/underscore? Similar to how _.compact(array) removes falsey elements from arrays
so from
{
propA: true,
propB: true,
propC: false,
propD: true,
}
returning
{
propA: true,
propB: true,
propD: true,
}
Here are two vanilla javascript options:
A.: Iterate over the object's keys and delete those having a falsey value.
var obj = {
propA: true,
propB: true,
propC: false,
propD: true,
};
Object.keys(obj).forEach(key => {
if (!obj[key]) delete obj[key];
});
console.log(obj);
See Object.keys() and Array.prototype.forEach()
B.: Iterate over the object's keys and add truthy values to a new object.
var obj = {
propA: true,
propB: true,
propC: false,
propD: true,
};
var filteredObj = Object.keys(obj).reduce((p, c) => {
if (obj[c]) p[c] = obj[c];
return p;
}, {});
console.log(filteredObj);
See Object.keys() and Array.prototype.reduce()
Lodash 4.0
Lodash 4.0 has _.pick, which takes an array of properties, and _.pickBy which takes a function as an argument and returns an object only containing the keys for which that function returns truthy which is what we want here, so it'd be:
filtered = _.pickBy(obj, function(value, key) {return value;})
Or, since _.pickBy defaults to using _.identity as it's second argument, (and that's essentially what we've written above,) it can just be written as:
filtered = _.pickBy(obj);
Underscore or Lodash prior to version 4.0
In underscore and old versions of lodash, there's just a single _.pick, which has both behaviors of _.pick and _.pickWith from v4. So you can do:
filtered = _.pick(obj, function(value, key) {return value;})
Or more succinctly:
filtered = _.pick(obj, _.identity)
Unfortunately I cannot direclty comment on the posts above yet, so I create this extra post.
Since Lodash v4 the functionality described above has been moved to _.pickBy. With _.identity as default you could also change your code to:
var filtered = _.pickBy(obj);
See this JSBin for a working example.
As partial mentioned in a comment, ES6 provided Object.entries() and in 2019 Object.fromEntries().
Allowing:
Object.fromEntries(Object.entries(obj).filter(([key, value]) => ...))
Ex:
const obj = {
a: 12,
b: 123,
};
const filteredObj = Object.fromEntries(
Object.entries(obj).filter(
([_, value]) => value > 100
)
);
console.log(filteredObj);
// {b: 123}
If you're using lodash, I'd recommend something like this:
var object = {
propA: true,
propB: true,
propC: false,
propD: true,
};
_.pick(object, _.identity);
// →
// {
// propA: true,
// propB: true,
// propD: true
// }
The pick() function generates a new object that includes properties that the callback returns truthy for. So we can just use the identity() function as the callback, since it'll just return each property value.
From lodash 4, we can use pickBy() to get only the value equal to true.
const active = _.keys(_.pickBy(object));
let temp = {
propA: true,
propB: true,
propC: false,
propD: true,
}
let obj = {}
for(x in temp){
if(temp[x] == true){
obj[x] = temp[x]
}
}
console.log(obj)
Using for-in loop we can achieve it something like this.
Another approach
const objFilter = (obj, condition) => {
let newObj = {}
for (const [key, value] of Object.entries(obj)) {
if (condition(value)) {
newObj = { ...newObj, [key]: value }
}
}
return newObj
}
Fire like this:
const newData = objFilter(oldData, (value) => value.marked === false)

Categories