Need a custom assignment implementaion - javascript

I am working with some state management application where I have a data structure as follows
const mainObject = {
firstLevel: {
secondLevel: {
thirdLevel: {
actualProperty: 'Secret'
}
}
},
firstLevelUntouched:{
secondLevelUntouched:{
thirdLevelUntouched:{
untouchedProperty:'I don`t want to change'
}
}
}
};
I want to change the actualProperty to a new value which out a deepClone
I did it with the following code
const modified = {
...mainObject,
...{
firstLevel: {
...mainObject.firstLevel,
...{
secondLevel: {
...mainObject.firstLevel.secondLevel,
thirdLevel: {
...mainObject.firstLevel.secondLevel.thirdLevel,
actualProperty: 'New secret'
}
}
}
}
}
}
But its looks like Bulky Code. So I need to write a function like
modified = myCustomAssignment(mainObject, ['firstLevel', 'secondLevel', 'thirdLevel', 'actualProperty'], 'New secret')
Can anyone help me on this?

You could use a simple traversal function for this that just traverses the passed properties until it arrives as the final one, then sets that to the new value.
function myCustomAssignment(mainObject, propertyList, newValue) {
const lastProp = propertyList.pop();
const propertyTree = propertyList.reduce((obj, prop) => obj[prop], mainObject);
propertyTree[lastProp] = newValue;
}
You could even add propertyList = propertyList.split('.') to the top of this function so the list can be passed in as an easy-to-read string, like myCustomAssignment(mainObject, 'firstLevel.secondLevel.thirdLevel.actualProperty', 'new value') if you wanted that.

export function mutateState(mainObject: object, propertyList: string[], newValue: any) {
const lastProp = propertyList.pop();
const newState: object = { ...mainObject };
const propertyTree =
propertyList
.reduce((obj, prop) => {
obj[prop] = { ...newState[prop], ...obj[prop] };
return obj[prop];
}, newState);
propertyTree[lastProp] = newValue;
return newState as unknown;
}
This fixed my issue. thanks all..

Related

How to observe property value changes of a third party object?

I would like to observe whenever a property of a third party object is changed. I'm taking the approach of assigning a custom setter but my console.log below is never invoked. Why is that? Is there a better approach?
const foo = { a: 1, b: 2 };
Object.assign(foo, {
set user(user) {
foo.user = user;
console.log(">>>>>> user was changed", user);
},
});
// Desired behaviour
foo.user = "asdf"; // >>>>>> user was changed asdf
delete foo.user; // >>>>>> user was changed undefined
foo.user = "asdf1" // >>>>>> user was changed asdf1
Please note, I need to mutate foo I cannot wrap a proxy around foo and return that because it is a third party library which mutates .user internally
I've found a way, pretty hacky as it is
const foo = { a: 1, b: 2 };
let underlyingValue = foo.user
Object.defineProperty(foo, "user", {
get() {
return underlyingValue
},
set(user) {
underlyingValue = user;
console.log(">>>>>> user was changed", user);
},
enumerable: true
});
foo.user = "asdf";
console.log(foo)
I've made this into a generic function below 👇
/** Intercepts writes to any property of an object */
function observeProperty(obj, property, onChanged) {
const oldDescriptor = Object.getOwnPropertyDescriptor(obj, property);
let val = obj[property];
Object.defineProperty(obj, property, {
get() {
return val;
},
set(newVal) {
val = newVal;
onChanged(newVal);
},
enumerable: oldDescriptor?.enumerable,
configurable: oldDescriptor?.configurable,
});
}
// example usage 👇
const foo = { a: 1 };
observeProperty(foo, "a", (a) => {
console.log("a was changed to", a);
});
foo.a = 2; // a was changed to 2
Also available in typescript
🚨 Edit: This will break if the property is deleted eg delete foo.user. The observer will be removed and the callback will stop firing. You will need to re-attach it.
#david_adler ... when I commented ...
"Is the latter a special case or does the OP need a somehow more generic observation approach?"
... I thought of the most generic solution one could come up with in terms of changing/mutating an existing object entirely into an observable variant of itself.
Such a solution also would be more close to what the OP did ask for ...
"I would like to observe whenever a property of a third party object is changed"
Thus the next provided approach keeps the objects appearance and behavior and also does not introduce additional (e.g. Symbol based) keys.
function mutateIntoObservableZombie(obj, handlePropertyChange) {
const propertyMap = new Map;
function createAccessors(keyOrSymbol, initialValue, handler) {
return {
set (value) {
propertyMap.set(keyOrSymbol, value);
handler(keyOrSymbol, value, this);
return value;
},
get () {
return propertyMap.has(keyOrSymbol)
? propertyMap.get(keyOrSymbol)
: initialValue;
},
};
}
function wrapSet(keyOrSymbol, proceed, handler) {
return function set (value) {
handler(keyOrSymbol, value, this);
return proceed.call(this, value);
};
}
function createAndAssignObservableDescriptor([keyOrSymbol, descriptor]) {
const { value, get, set, writable, ...descr } = descriptor;
if (isFunction(set)) {
descr.get = get;
descr.set = wrapSet(keyOrSymbol, set, handlePropertyChange);
}
if (descriptor.hasOwnProperty('value')) {
Object.assign(descr, createAccessors(keyOrSymbol, value, handlePropertyChange));
}
Object.defineProperty(obj, keyOrSymbol, descr);
}
const isFunction = value => (typeof value === 'function');
if (isFunction(handlePropertyChange)) {
const ownDescriptors = Object.getOwnPropertyDescriptors(obj);
const ownDescrSymbols = Object.getOwnPropertySymbols(ownDescriptors);
Object
.entries(ownDescriptors)
.forEach(createAndAssignObservableDescriptor);
ownDescrSymbols
.forEach(symbol =>
createAndAssignObservableDescriptor([symbol, ownDescriptors[symbol]])
);
}
return obj;
}
// third party object (closed/inaccessible code)
const foo = { a: 1, b: 2 };
// custom changes already.
foo.userName = '';
foo.userLoginName = '';
const userNick = Symbol('nickname');
foo[userNick] = null;
console.log('`foo` before descriptor change ...', { foo });
mutateIntoObservableZombie(foo, (key, value, target) => {
console.log('property change ...', { key, value, target });
});
console.log('`foo` after descriptor change ...', { foo });
foo.a = "foo bar";
foo.b = "baz biz";
console.log('`foo` after property change ...', { foo });
foo.userName = '****';
foo.userLoginName = '************#**********';
console.log('`foo` after property change ...', { foo });
foo[userNick] = 'superuser';
console.log('`foo` after symbol property change ...', { foo });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit
Since the above approach already is implemented generic and modular it of cause easily can be refactored into a function which allows the exact definition of which property/ies, both string and symbol based, are going to be observed ...
function observePropertyChange(obj, keysAndSymbols, handlePropertyChange) {
const propertyMap = new Map;
function createAccessors(keyOrSymbol, initialValue, handler) {
return {
set (value) {
propertyMap.set(keyOrSymbol, value);
handler(keyOrSymbol, value, this);
return value;
},
get () {
return propertyMap.has(keyOrSymbol)
? propertyMap.get(keyOrSymbol)
: initialValue;
},
};
}
function wrapSet(keyOrSymbol, proceed, handler) {
return function set (value) {
handler(keyOrSymbol, value, this);
return proceed.call(this, value);
};
}
function createAndAssignObservableDescriptor(keyOrSymbol, descriptor) {
const { value, get, set, writable, ...descr } = descriptor;
if (isFunction(set)) {
descr.get = get;
descr.set = wrapSet(keyOrSymbol, set, handlePropertyChange);
}
if (descriptor.hasOwnProperty('value')) {
Object.assign(descr, createAccessors(keyOrSymbol, value, handlePropertyChange));
}
Object.defineProperty(obj, keyOrSymbol, descr);
}
const isString = value => (typeof value === 'string');
const isSymbol = value => (typeof value === 'symbol');
const isFunction = value => (typeof value === 'function');
if (isFunction(handlePropertyChange)) {
const ownDescriptors = Object.getOwnPropertyDescriptors(obj);
const identifierList = (Array
.isArray(keysAndSymbols) && keysAndSymbols || [keysAndSymbols])
.filter(identifier => isString(identifier) || isSymbol(identifier));
identifierList
.forEach(keyOrSymbol =>
createAndAssignObservableDescriptor(keyOrSymbol, ownDescriptors[keyOrSymbol])
);
}
return obj;
}
// third party object (closed/inaccessible code)
const foo = { a: 1, b: 2 };
// custom changes already.
foo.userName = '';
foo.userLoginName = '';
const userNick = Symbol('nickname');
foo[userNick] = null;
console.log('`foo` before descriptor change ...', { foo });
observePropertyChange(
foo,
['b', 'userLoginName', userNick],
(key, value, target) => { console.log('property change ...', { key, value, target }); },
);
console.log('`foo` after descriptor change ...', { foo });
foo.a = "foo bar";
foo.b = "baz biz";
console.log('`foo` after property change ...', { foo });
foo.userName = '****';
foo.userLoginName = '************#**********';
console.log('`foo` after property change ...', { foo });
foo[userNick] = 'superuser';
console.log('`foo` after symbol property change ...', { foo });
.as-console-wrapper { min-height: 100%!important; top: 0; }

Sharing state when applying Douglas Crockford's composition pattern

This is the form of constructor which Douglas Crockford suggests in his book "How Javascript works" and in his lectures.
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({ method_x })
}
He suggests the following pattern for composition:
const constructor_y = function (spec) {
let { b } = spec // private state
// we can call other constructor and borrow functionality
const { method_x } = constructor_x(spec)
// we define new methods
const method_y = function () { b = '...' }
// we can merge borrowed and new functionality
// and expose everything as public interface
return Object.freeze({ method_x, method_y })
}
So here we see how to compose constructor_x and constructor_y. But my problem with this example (and all examples used when this pattern is presented) is that constructor_x and constructor_y make separate private states. constructor_x works on variable a, while constructor_y works on variable b. What if we want our constructors to share state? What if constructor_y also wants to work with variable a?
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () {
// we may want to read `a` and maybe write to it
a = '...'
}
return Object.freeze({ method_x, method_y, method_z })
}
Of course this doesn't achieve what I want because a which constructor_y sees is not the same a constructor_x sees. If I used this, I could have achieved that maybe like so:
const constructor_x = function (spec) {
return {
_a: spec.a,
method_x () { this._a = '...' }
}
}
const constructor_y = function (spec) {
return {
...constructor_x(spec),
_b: spec.b
method_y () { this._b = '...' },
method_z () { this._a = '...' }
}
}
But here I have lost privacy of variables _a and _b since they are attached to instance and are accessible just like methods. The best I can do is add underscore prefix which Douglas Crockford calls a sign of incompetence. I also lost instance's rigidness because it can no longer be frozen.
I could have exposed accessors for variable a in constructor_x like so:
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({
method_x,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () { set_a('...') }
return Object.freeze({ method_x, method_y, method_z })
}
These accessors can now be used by constructor_y to access private state of constructor_x. They are something like protected members in classical inheritance model. This makes constructor_x in some way special: It is not to be used as normal constructor, but only for composition inside other constructors. Another problem is that if we had another constructor like constructor_x which works on private variable a, we couldn't use them together in composition:
// another constructors which wants to work on `a`
const constructor_x2 = function (spec) => {
let { a } = spec
const method_z = function () { a = '...' }
return Object.freeze({
method_z,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const { method_x2, get_a: get_a2, set_a: set_a2 } = constructor_x2(spec)
// How do I use variable a now? There are two of them
// and constructors x and x2 don't share them.
}
All of this would not be a problem if I used this and modified state on the instance.
From my comments above ...
"1/2 ... First, all this creator functions should be referred to as factories or factory functions. They are not constructors. ... What if we want our constructors to share state? " ... then just implement the factories in a way that they can share each their entire inner/encapsulated state object and/or that they aggregate a shared state object while running the object creation process (the chained invocation of related functions during the composition process)."
What the OP wants to achieve can not be entirely covered by closure creating factory functionality according to Crockford / provided by the OP.
Encapsulated but shared (thus also mutable) state amongst "Function based Composable Units of Reuse" gets achieved best by a single factory which takes care of the composition process by invoking one or more mixin like functions which in addition to the to be shaped / aggregated type (the latter should carry public methods only) also need to get passed the type's local state(which will be accessed by the types's public methods).
function withActionControl(type, actionState) {
actionState.isInAction = false;
return Object.assign(type, {
monitorActions() {
const {
isInAction,
...otherActions } = actionState;
return { ...otherActions };
},
});
}
function withCanSwimIfNotBlocked(type, state) {
state.isSwimming = false;
return Object.assign(type, {
startSwimming() {
if (!state.isInAction) {
state.isInAction = true;
state.isSwimming = true;
console.log({ startSwimming: { state } })
}
},
stopSwimming() {
if (state.isSwimming) {
state.isInAction = false;
state.isSwimming = false;
console.log({ stopSwimming: { state } })
}
},
});
}
function withCanFlyIfNotBlocked(type, state) {
state.isFlying = false;
return Object.assign(type, {
startFlying() {
if (!state.isInAction) {
state.isInAction = true;
state.isFlying = true;
console.log({ startFlying: { state } })
}
},
stopFlying() {
if (state.isFlying) {
state.isInAction = false;
state.isFlying = false;
console.log({ stopFlying: { state } })
}
},
});
}
function withLaysEggsIfNotBlocked(type, state) {
state.isLayingEggs = false;
return Object.assign(type, {
startLayingEggs() {
if (!state.isInAction) {
state.isInAction = true;
state.isLayingEggs = true;
console.log({ startLayingEggs: { state } })
}
},
stopLayingEggs() {
if (state.isLayingEggs) {
state.isInAction = false;
state.isLayingEggs = false;
console.log({ stopLayingEggs: { state } })
}
},
});
}
function createSeabird(type) {
const birdState = {
type,
actions: {},
};
const birdType = {
valueOf() {
return JSON.parse(
JSON.stringify(birdState)
);
},
};
const { actions } = birdState;
withActionControl(birdType, actions)
withLaysEggsIfNotBlocked(birdType, actions);
withCanFlyIfNotBlocked(birdType, actions);
withCanSwimIfNotBlocked(birdType, actions);
return birdType;
}
const wisdom = createSeabird({
family: 'Albatross',
genus: 'North Pacific albatross',
species: 'Laysan albatross',
name: 'Wisdom',
sex: 'female',
age: 70,
});
console.log({ wisdom });
console.log('wisdom.valueOf() ...', wisdom.valueOf());
console.log('wisdom.monitorActions() ...', wisdom.monitorActions());
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.stopFlying();')
wisdom.stopFlying();
console.log('wisdom.stopFlying();')
wisdom.stopFlying();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.stopSwimming();')
wisdom.stopSwimming();
console.log('wisdom.stopSwimming();')
wisdom.stopSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.valueOf() ...', wisdom.valueOf());
console.log('wisdom.monitorActions() ...', wisdom.monitorActions());
.as-console-wrapper { min-height: 100%!important; top: 0; }
Close with one of the above initial comments ...
"2/2 ... Just take advantage of the language's flexibility and expressiveness. Just be aware of the advantages, pitfalls and comprehensibility (to others) of your modeling approach(es). And once this is checked don't worry about [too strict]* Crockford disciples (or any other school / religion / cult). A good teacher shows you a [path]* and allows / encourages you to discover or follow your own, once you understood what the base/basics are good for."

How to use reduce function in a nested state object in reactJS?

My state value is
this.state = {
content: {
text: {
tag1: {
line: "data1"
}
tag2: {
line: "data2"
}
}
}
}
How can I use javascript reduce() function to change the value of line of both tag1 and tag2 to "changed text"?
Here you are:
this.setState(prevState => {
return {
content: {
...prevState.content,
text: Object.keys(prevState.content.text).reduce((newTexts, key) => {
return {
...newTexts,
[key]: {
line: "changed text"
}
}
}, {})
}
}
});
You should you setState with a function so you don't change state directly.
this.setState(prevState => {
for(let k in prevState.content.text){
prevState.content.text[k].line = "changed";
}
return {content: prevState.content}
}
Edit:
I'm not sure if changing prevState directly is a good thing (please some one correct me), but you can also do
this.setState(prevState => {
let changedState = {...prevState}
for(let k in changedState.content.text){
changedState.content.text[k].line = "changed";
}
return {content: changedState.content}
}
Edit:
As said in the comments, {...prevState} is going to be a shallow copy and it can still change the state directly. One solution to this is use lodash cloneDeep
I think using for..in will be better.
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
}
for(let k in state.content.text){
state.content.text[k].line = "changed";
}
console.log(state)
I don't think Array#prototype#reduce would be a fit.
You can use a plain modern for...of loop with Object.entries and do this:
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
};
for (const obj of Object.entries(state.content.text)) {
obj[1].line = 'Changed text';
}
console.log(state);
For acheiving an immutable state you can do Object.assign before mutating the new object's properties.
const state = {
content: {
text: {
tag1: {
line: "data1"
},
tag2: {
line: "data2"
}
}
}
};
// Create a new object from an existing object
const newState = Object.assign(state, {});
Object.entries(newState.content.text).forEach(x => {
x[1].line = 'Changed text';
});
console.log(newState);

ES6 dynamic destructuring

I need to apply different destructuring for the function response depending of global flag [single service for multiple apps]
// Destructuring template should be defined as value
let destructuringTemplate;
if (flag) {
destructuringTemplate = {data: {classA: user}};
} else {
destructuringTemplate = {data: {classB: user}};
}
// This technique would not work, this is just my idea representation.
this.getUser(({destructuringTemplate: user) => { this.localUser = user });
At this moment it works this way:
let destructuringTemplate;
if (flag) {
destructuringTemplate = ({data: {classA: user}}) => user;
} else {
destructuringTemplate = ({data: {classB: user}}) => user;
}
this.getUser(response => { this.localUser = destructuringTemplate(response)};
it is kinda ugly, some suggestion how it should be done?
You could use a computed property name with a conditional (ternary) operator ?:.
var flag = true,
object = { data: { classA: 'foo', classB: 'bar' } },
{ data: { [flag ? 'classA' : 'classB']: user } } = object,
{ data: { [!flag ? 'classA' : 'classB']: user1 } } = object;
console.log(user);
console.log(user1);
You don't need to use destructuring, use simple dot/bracket notation:
const userClass = flag ? 'classA' : 'classB'
this.getUser(response => { this.localUser = response.data[userClass] })
If you want to reuse this logic, just create simple function, e.g.:
const extractUser = response => response.data[userClass]

How to turn nested plain js objects into Ember.js objects?

If I have a nested set of plain old javascript objects (for example, having been returned from JSON), how do I them into Ember.js objects (or at least getting the binding functionality working)?
For example, if I have an object like:
var x = {
bar: {
baz: "quux"
}
}
Then I turn that into an Ember object:
var y = Ember.Object.create(x);
Then setting the value of "baz" won't update any views I have, because it is just a normal js object, not an Ember object.
I know I can just recursively go over the object keys, and do Ember.Object.create all the way down, but is there an alternative approach?
I'm not sure how you're attempting to set the value of baz, after you've created the Ember.Object, but you should make sure you use an observer-aware setter function. For this example, I'd suggest using setPath().
For example:
var x = {
bar: {
baz: "quux"
}
};
var y = Ember.Object.create(x);
y.setPath('bar.baz', 'foo');
jsFiddle example, showing a view update after setting: http://jsfiddle.net/ebryn/kv3cU/
Here's my version:
import { typeOf } from '#ember/utils'
import EmberObject from '#ember/object'
export default function deepEmberObject(anything) {
if (typeOf(anything) === 'array') {
return anything.map(a => deepEmberObject(a))
} else if (typeOf(anything) === 'object') {
let converted = Object.keys(anything).reduce((acc, k) => {
acc[k] = deepEmberObject(anything[k])
return acc
}, {})
return EmberObject.create(converted)
} else {
return anything
}
}
test:
import deepEmberObject from 'zipbooks/utils/deep-ember-object'
import { module, test } from 'qunit'
module('Unit | Utility | deep-ember-object', function() {
test('it works', function(assert) {
let result = deepEmberObject({ pandas: [{ id: 3, children: [{ name: 'Bobby', features: { weight: 3 } }] }] })
assert.equal(
result
.get('pandas')[0]
.get('children')[0]
.get('features')
.get('weight'),
3
)
})
})
For some reason I had to define nested objects independently in order to ensure the computed works properly (even the enumerated ones).
For that I end up crafting these 2 utility functions:
import EmberObject from '#ember/object';
import { A } from '#ember/array';
function fromArrayToEmberArray(array) {
const emberArray = A();
array.forEach(function(item) {
if (Array.isArray(item)) {
emberArray.push(fromArrayToEmberArray(item));
} else if (item && typeof item === 'object') {
emberArray.push(fromObjectToEmberObject(item));
} else {
emberArray.push(item);
}
});
return emberArray;
}
function fromObjectToEmberObject(pojo) {
const emberObject = EmberObject.create();
for (const key in pojo) {
const keyObject = pojo[key];
if (Array.isArray(keyObject)) {
emberObject.set(key, fromArrayToEmberArray(keyObject))
} else if (keyObject && typeof keyObject === 'object') {
emberObject.set(key, fromObjectToEmberObject(keyObject))
} else {
emberObject.set(key, keyObject);
}
}
return emberObject;
}
export default {fromObjectToEmberObject};

Categories