/** * Callback whenever a property in the object changes * @callback onChangeCallback * @param {*} newValue * @param {*} oldValue * @param {String} path */ /** * Creates a new object that is watched for changes * Works recursively as long as children are objects * Will not work for newly defined properties * * NOTE: Returning a new object instead of modifying * orignal boosts performance by over 20x * * @param {Object} obj * @param {onChangeCallback} callback * @return {Object} */ const onChange = (obj, callback, pathPrefix = "") => { const newObj = {}; const privateData = {}; const keys = Object.keys(obj); for (let i = 0; i < keys.length; ++i) { const key = keys[i]; let value = obj[key]; if (typeof value === "object") { value = onChange(value, callback, `${key}.`); } // save key to privateData privateData[key] = value; // replace key with getter/setter pointing to privateData Object.defineProperty(newObj, key, { enumerable: true, get() { return privateData[key]; }, set(newValue) { const oldValue = privateData[key]; privateData[key] = newValue; callback(newValue, oldValue, pathPrefix + key); }, }); } return newObj; }; export default onChange;