The utility for immutable merging objects with actions helps.
When using Redux, the good practice is always return a new object from state rather than mutate the existing one because of the shallow comparing. It helps avoid redundant components's re-rendering.
The usual patterns that I observed when developers using Redux is:
const reducer = (state, { type, payload }) => {
switch (type) {
case 'INIT':
return payload;
case 'ADD_ITEM':
return { ...state, items: [...state.items, payload] };
case 'CHANGE_TITLE':
return { ...state, title: payload };
case 'CHANGE_NAME':
return { ...state, name: payload };
default:
return state;
}
};
There is two problems with the above implementation:
- It looks the same and repetitive for many reducers, the main purpose is still merging objects in the immutable manner.
- Deeply nested object update will be even harder.
We could rewrite it as:
import imMerge from 'im-merge';
/*
* payload could be:
* { title: 'new title' }
* { name: 'new name' }
* { items: ['new item'] }
* */
const reducer = (state, { type, payload }) => {
switch (type) {
case 'INIT':
return payload;
case 'CHANGE':
return imMerge(state, payload);
default:
return state;
}
};
npm i im-merge
ES6
import imMerge, { insertType } from 'im-merge';
const data = {x : { y : { z: 3, k: 7 } }, items: [{}, 2, 3, 4] };
const source = {x : { y : { z: 4 }, t: { q : 6 } }, items: insertType(5, 1) };
const result = imMerge(data, source);
console.log(result);
/*
{x : { y : { z: 4, k: 7 }, t: { q : 6 } }, items: [{}, 5, 2, 3, 4] };
*/
console.log(source.t === result.t); // true
console.log(source.items[0] === result.items[0]); // true -> object reference kept
console.log(source.t === result.t); // true
Main function:
-
func imMerge(data: any, source: any) => any
return a immutable value/object by recursively merging data and source.
- if data and source are different in type(object vs array, ...) then return source.
- if data and source are the same in type(array/array, object/object, primitive/primitive) then do the merge.
- if (array/array) case: the default behavior is returning the concatenate array, in order to handle more complex user case, we could use helpers modification types(insertType, insertFisrtType, insertLastType, insertBeforeMatchType, insertAfterMatchType, removeType, removeFirstType, removeLastType, removeMatchType);
Array modification types:
-
func mergeType(data) => object
return immutable array with deeply merged items.
import imMerge, { insertType } from 'im-merge';
const data = { items: [{x: 1}, {x: 2}, {x: 3}] };
const source = { items: mergeType([{y: 1}, {y: 2}]) };
const result = imMerge(data, source);
// { items: [{x: 1, y: 1}, {x: 2, y: 2}, {x: 3}] }
-
func insertType(data, index = 0, flatten = true) => object
return immutable array with inserted item at given index.
import imMerge, { insertType } from 'im-merge';
const data = { items: [{x: 1}, {x: 2}, {x: 3}] };
const source = { items: insertType([{x: 4}, {x: 5}], 1) };
const result = imMerge(data, source);
// { items: [{x: 1}, {x: 4}, {x: 5}, {x: 2}, {x: 3}] }
-
func insertFisrtType(data, flatten = true) => object
instruction for inserting new item at the beginning of the array
-
func insertLastType(data, flatten = true) => object
instruction for inserting new item at the end of the array
-
func insertBeforeMatchType(match, data, flatten = true) => object
instruction for inserting new item before the index of matched item(deep contain match) in the array
import imMerge, { insertBeforeMatchType } from 'im-merge';
const data = { items: [{x: 1}, {x: 2, y: {z: 3}}, {x: 3, y: {z: 4}}] };
const source = { items: insertBeforeMatchType([{x: 4}, {x: 5}], {y : {z: 3}}) };
const result = imMerge(data, source);
// { items: [{x: 1}, {x: 4}, {x: 5}, {x: 2, y: {z: 3}}, {x: 3, y: {z: 4}}] }
-
func insertAfterMatchType(match, data, flatten = true) => object
instruction for inserting new item after the index of matched item(deep contain match) in the array
-
func removeType(index = 0) => object
instruction for removing item at the index in the array
-
func removeFirstType() => object
instruction for removing item at the first index in the array
-
func removeLastType() => object
instruction for removing item at the last index in the array
-
func removeMatchType(match) => object
instruction for removing item at the matched index in the array