I would like to use mergeDeep to merge my deeply nested state object, but not try to merge the Lists whenever it sees them
Simple example. More detailed sample below.
var originalData = {person: { name: 'John', skills: ['drinking', 'eating']}};
var originalState = Immutable.fromJS(originalData);
var updatedData = {person: {name: 'John Doe', skills: ['sleeping']}};
var updatedState = originalState.mergeDeep(updatedData);
updatedState.toJS().person.skills; // ["sleeping", "eating"]
I need the arrays to be ovewritten, not merged. I understand that mergeDeep is designed that way. I am just wondering if there's an easier way to achieve what I need. Right now I am doing this:
updatedData object, find properties that are Array.isArraysetIn(), passing that arrayupdatedData to lighten the load for mergeDeepjs
// mergeDeep that replaces arrays with instead of merging
function mergeDeepReplaceArrays (state, updatedData) {
const replaceArrays = (data, basePath) => {
basePath = basePath || [];
_.forIn(data, (v, k) => {
const path = basePath.concat([k]);
if (Array.isArray(v)) {
if (state.hasIn(path)) {
state = state.setIn(path, Immutable.fromJS(v));
delete data[k];
}
} else if (_.isObject(v)) {
replaceArrays(v, path);
}
});
};
replaceArrays(updatedData);
return state.mergeDeep(updatedData);
}
This definitely seems hacky and weird. Would be nice, I think, to have a hook into mergeDeep, similar to mergeDeepWith, except for when the optional merger will be called for every node, giving original/target rather than just on the conflicts.
Edit: Updated the code to use withMutations, making it a bit faster, but for some weird reasons, my hacky/crappy code is faster than plain mergeDeep, according to jsPerf: http://jsperf.com/withmutations-test
More detailed sample
// Initial entities
var entities = Immutable.fromJS({
products: {
'1': {
name: 'iPhone',
categories: ['tech', 'phone', 'mobile']
},
'2': {
name: 'Apple',
categories: ['food', 'fruit']
},
'3': {
name: 'Jacket X',
categories: ['clothing']
}
},
// list of product ids
carts: {
'1': {
name: 'First Cart',
items: [1, 2, 3]
},
'2': {
name: 'Second Cart',
items: [3]
}
}
})
A change occured to some items:
iPhonetech category removedFirst Cart:2 is removedvar entitiesUpdate = {
products: {
'1': {
name: 'iPhone',
categories: ['phone', 'mobile']
}
}
carts: {
'1': {
name: 'First Cart updated',
items: [1, 3]
}
}
}
I need the arrays at products['1'].categories and carts['1'].items to be replaced to properly reflect the update, not merged into ['phone', 'mobile', 'mobile'] and [1, 3, 3] respectively.
Best to ask this type of question on Stack Overflow where there is more traffic. https://stackoverflow.com/questions/tagged/immutable.js?sort=votes
The answer likely includes mergeDeepWith which allows for a custom merge function.
mergeDeepWith actually only interacts when 2 elements are non-iterable I think.
I got it to work with mergeWith as follow
import { List } from 'immutable'
const isList = List.isList
export default function merger(a, b) {
if (a && a.mergeWith && !isList(a) && !isList(b)) {
return a.mergeWith(merger, b)
}
return b
}
I was looking for something similar. I will just place my modification to @ntgn81 code here.
import { fromJS, List, Map, Set } from 'immutable';
import { Path } from '../types';
// mergeDeep that merges arrays as sets instead or overwriting it. If only fromJS converted to Set instead of List...
export function mergeDeepArrays (state: any, value: any): any {
const mergeArrays = (data: Map<string, any>, basePath: Path) => {
let next: Map<string, any> = data;
data.forEach((v, k) => {
const path = basePath.concat([<string> k]);
if (List.isList(v)) {
if (state.hasIn(path)) {
const current = state.getIn(path);
if (List.isList(current) && !current.isEmpty()) {
state = state.setIn(path, Set(current).merge(v));
next = next.deleteIn(path);
}
}
} else if (Map.isMap(v)) {
next = next.setIn(path, mergeArrays(v, path));
}
});
return next;
};
const updatedData = mergeArrays(fromJS(value), []);
return state.mergeDeep(updatedData);
}
export default mergeDeepArrays;
Most helpful comment
mergeDeepWith actually only interacts when 2 elements are non-iterable I think.
I got it to work with
mergeWithas follow