Skip to content Skip to sidebar Skip to footer

Redux, Normalised Entities And Lodash Merge

I'm using Redux, React and Lodash with a fairly standard normalized entities store. When I merge in new entities in a redux reducer, the references to all my existing entities chan

Solution 1:

Use mergeWith with a customizer:

letkeepRef = (objValue, srcValue) => (
  objValue === undefined ? srcValue : _.mergeWith({}, objValue, srcValue, keepRef)
)
let newEntities = _.mergeWith({}, entities, response, keepRef)

Solution 2:

I expanded on @Pavlo's awesome answer. I added support for arrays, and collections. I define a collection as an array of object's, where each object has an id key. This is very common in react/redux and normalized data.

import { mergeWith, isPlainObject, isEmpty, keyBy } from 'lodash'// https://stackoverflow.com/a/49437903/1828637// mergeWith customizer.// by default mergeWith keeps refs to everything,// this customizer makes it so that ref is only kept if unchanged// and a shallow copy is made if changed. this shallow copy continues deeply.// supports arrays of collections (by id).
function keepUnchangedRefsOnly(objValue, srcValue) {
    if (objValue === undefined) { // do i need this?return srcValue;
    } elseif (srcValue === undefined) { // do i need this?return objValue;
    } elseif (isPlainObject(objValue)) {
        return mergeWith({}, objValue, srcValue, keepUnchangedRefsOnly);
    } elseif (Array.isArray(objValue)) {
        if (isEmpty(objValue) && !isEmpty(srcValue))return [...srcValue];
        elseif (!isEmpty(objValue) && isEmpty(srcValue)) return objValue;
        elseif (isEmpty(objValue) && isEmpty(srcValue)) return objValue; // both emptyelse {
            // if array is array of objects, then assume each object has id, and merge based on id// so create new array, based objValue. id should match in each spotif (isPlainObject(objValue[0]) && objValue[0].hasOwnProperty('id')) {
                const srcCollection = keyBy(srcValue, 'id');

                const aligned = objValue.map(el => {
                    const { id } = el;
                    if (srcCollection.hasOwnProperty(id)) {
                        const srcEl = srcCollection[id];
                        delete srcCollection[id];
                        return mergeWith({}, el, srcEl, keepUnchangedRefsOnly);
                    } else {
                        return el;
                    }
                });

                aligned.push(...Object.values(srcCollection));

                return aligned;
            } else {
                return [ ...objValue, ...srcValue ];
            }
        }
    }
}

Usage:

const state = {
    chars: ['a', 'b'],
    messages: [
        {
            id: 1,
            text: 'one'
        },
        {
            id: 2,
            text: 'ref to this entry will be unchanged'
        }
    ]
}

const response = {
    chars: ['c', 'd'],
    messages: [
        {
            id: 1,
            text: 'changed ref text one'
        },
        {
            id: 3,
            text: 'three'
        }
    ]
}

const stateNext = mergeWith({}, state, response, keepUnchangedRefsOnly)

Resulting stateNext is:

{
    chars: [
        'a',
        'b',
        'c',
        'd'
    ],
    messages: [
        {
            id: 1,
            text: 'changed ref text one'
        },
        {
            'id': 2,
            text: 'ref to this entry will be unchanged'
        },
        {
            'id': 3,
            text: 'three'
        }
    ]
}

If you want to keep undefined values, then replace mergeWith in customizer and your use case with assignWith. Example - https://stackoverflow.com/a/49455981/1828637

Post a Comment for "Redux, Normalised Entities And Lodash Merge"