/* Helper functions that can be used in componentDidUpdate to find what changed between component updates. Example: componentDidUpdate(prevProps, prevState) { const propsChanged = changes(prevProps, this.props); const stateChanged = changes(prevState, this.state); const propsDiff = diff(prevProps, this.props); const stateDiff = diff(prevState, this.state); if (propsChanged.someDataThing) { this.doStuff(propsDiff.someDataThing); } if (stateChanged.intermediateValue) { this.continueThing(); } } */ // Copyright (c) 2021 Jonathan Voss // BSD0 license // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD // TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN // NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL // DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER // IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // compares two objects and returns recursive boolean flags for what properties are different // flags will not appear for any non-array without a difference in content // flag will be false for two equivalent arrays that are different instances export function changes (obj1, obj2) { let flags = {}; Object.keys(obj2).forEach(k => { if ((!obj1[k] && !!obj2[k]) || obj1[k] !== obj2[k]) { if (typeof obj1[k] == 'undefined' || typeof obj2[k] == 'undefined' || obj1[k] == null || obj2[k] == null || obj1[k].constructor.name !== obj2[k].constructor.name) { flags[k] = true; } else { switch (obj1[k].constructor.name) { case 'Array': flags[k] = (obj2[k].constructor.name == 'Array') ? obj1[k].filter(x => !obj2[k].includes(x)).length > 0 : true; break; case 'Object': flags[k] = changes(obj1[k], obj2[k]); break; default: flags[k] = true; } } } }); Object.keys(obj1).forEach(k => { if ((!!obj1[k] && !obj2[k]) || obj1[k] !== obj2[k]) { if (typeof obj1[k] == 'undefined' || typeof obj2[k] == 'undefined' || obj1[k] == null || obj2[k] == null || obj1[k].constructor.name !== obj2[k].constructor.name) { flags[k] = true; } else { switch (obj2[k].constructor.name) { case 'Array': flags[k] = (obj1[k].constructor.name == 'Array') ? obj2[k].filter(x => !obj1[k].includes(x)).length > 0 : true; break; case 'Object': flags[k] = changes(obj1[k], obj2[k]); if (flags[k].constructor.name == 'Object' && Object.keys(flags[k]).length < 1) { delete flags[k]; } break; default: flags[k] = true; } } } }); return flags; } // compares two objects and returns whatever properties were overwritten by obj2 export function diff (obj1, obj2) { let flags = changes(obj1, obj2); let difference = {}; Object.keys(flags).forEach(k => { if (typeof obj1[k] == 'undefined' || typeof obj2[k] == 'undefined' || obj1[k] == null || obj2[k] == null || obj1[k].constructor.name !== obj2[k].constructor.name) { difference[k] = obj2[k]; return; } switch (obj1[k].constructor.name) { case 'Array': if (obj2[k].constructor.name) difference[k] = obj2[k].filter(x => !obj1[k].includes(k)); else difference[k] = obj2[k]; break; case 'Object': difference[k] = diff(obj1[k], obj2[k]); if (Object.keys(difference[k]).length < 1) { delete difference[k]; } break; case 'Number': case 'Boolean': case 'Boolean': case 'String': default: difference[k] = obj2[k]; } }); return difference; }