es6: what is the value of result using spread operator? - javascript

came across this code learning redux and react.
my question is what the final result looks like when the spread oeprator is used. if I understand it correctly it basically turns iterable or array into individual arguments.
so I expect that the output is just creating another JSON object using all the fields obtained via the ... operator.
const INITIAL_STATE = { postsList: {posts: [], error:null, loading: false},
newPost:{post:null, error: null, loading: false},
activePost:{post:null, error:null, loading: false},
deletedPost: {post: null, error:null, loading: false},
};
export default function(state = INITIAL_STATE, action) {
let error;
switch(action.type) {
case FETCH_POSTS:// start fetching posts and set loading = true
return { ...state, postsList: {posts:[], error: null, loading: true} };
so is this the result of FETCH_POSTS:
{
postsList: {posts: [], error:null, loading: false},
newPost:{post:null, error: null, loading: false},
activePost:{post:null, error:null, loading: false},
deletedPost: {post: null, error:null, loading: false, },
so basically it was smart enough to know that there existed the postsList key and overwritten it?
now is it anti-pattern to rely on checking the existence of the state for the react.js app? meaning "if this key exists do this" instead of "if key value is null do this"
why don't you just change the value of the key via array key? INITIAL_STATE['postsList'] = {...postsObject}
or is using the spread operator the new pattern?

When using Redux you want to treat the state as immutable. The reducer should return a new state object, not modify the existing one. If you run INITIAL_STATE['postLists'] = somethingNew; you are modifying the object referenced by INITIAL_STATE.
Instead of using the spread operator in this case is to use Object.assign, which will create a new object based on an existing object and overload some properties.
That being said, you should not really use INITIAL_STATE at all except as the default value of state in the reducer definition.

Object.assign and ... spread operator is good for shallow copying. For deep copying, I prefer lodash/deepClone or deepCopy util like this.
export default function deepCopy(obj) {
if (typeof obj === 'object' && Object.prototype.toString.call(obj) === '[object Array]') {
let finalObj = [];
for (let i = 0, len = obj.length; i < len; ++i) {
finalObj.push(deepCopy(obj[i]));
}
return finalObj
} else if (typeof obj === 'object') {
let finalObj = {};
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
finalObj[prop] = deepCopy(obj[prop]);
}
}
return finalObj;
} else {
return obj;
}
}
I blindly deep-copy my state to a new state. And then apply Object.assign to various objects. So, my code will be:
const newState = deepCopy(state);
Object.assign(newState.prop1, { innerProp: 'val' });

Related

How do you make an JavaScript object's properties enumerable?

I have an object of type ServiceWorkerRegistration that I want to stringify:
const reg = await navigator.serviceWorker.register(`https://www.myurl.com/Worker.js`, {scope:"/"});
When I console.log(reg) I get the following:
ServiceWorkerRegistration { installing: null, waiting: null, active: ServiceWorker, navigationPreload: NavigationPreloadManager, scope: "https://www.myurl.com/", updateViaCache: "imports", onupdatefound: null, pushManager: PushManager }
active: ServiceWorker { scriptURL: "https://www.myurl.com/Worker.js", state: "activated", onstatechange: null, … }
onerror: null
onstatechange: null
scriptURL: "https://www.myurl.com/Worker.js"
state: "activated"
<prototype>: ServiceWorkerPrototype { postMessage: postMessage(), scriptURL: Getter, state: Getter, … }
installing: null
navigationPreload: NavigationPreloadManager { }
onupdatefound: null
pushManager: PushManager { }
scope: "https://www.myurl.com/"
updateViaCache: "imports"
waiting: null
<prototype>: ServiceWorkerRegistrationPrototype { update: update(), unregister: unregister(), showNotification: showNotification(), … }
When I do typeof reg I get 'object'
However if I try to JSON.stringify(reg) I get {}. Likewise if I try and do Object.keys(reg) I get [].
I looked at answers such as
Why does JSON.stringify return empty object notation "{}" for an object that seems to have properties? and
Why does JSON.stringify on TypeError return an empty object which claims this happens when an object has no enumerable properties.
A band aid solution is that I manually print each field e.g. console.log(reg["installing"]);, or to manually reset each property as enumerable e.g. something like Object.defineProperty(reg, 'installing', {enumerable: true}) for each property.
However I would like to be able to do something like:
Object.keys(reg).forEach(key=>console.log(reg[key]));
And to do so, the object must have enumerable properties. How do I make the properties enumerable?
From the comments my working solution is to just make a copy of the object (solution in TS):
const objCpy = (obj : any) : string =>{
let newObj : any = {}
const keys : string[] = Object.keys(Object.getPrototypeOf(obj));
keys.forEach((key : string)=>{
let val : any = obj[key]
if(typeof val === "object" && val !== null){
val = objCpy(val)
}
newObj[key] = val;
})
return newObj;
}
then it stringifies as expected:
JSON.stringify(objCpy(reg))
>> `{"installing":{"scriptURL":"http://localhost:3001/Worker.js","state":"installing","onstatechange":null,"onerror":null},"waiting":null,"active":null,"navigationPreload":{},"scope":"http://localhost:3001/","updateViaCache":"imports","onupdatefound":null,"pushManager":{}}`

Merge function return into bbject: Spread vs Object.assign [duplicate]

I'm reading an introduction to Redux reducers (https://redux.js.org/introduction/three-principles) which contains the following example of a reducer:
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false
}
]
case 'COMPLETE_TODO':
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: true
})
}
return todo
})
default:
return state
}
}
It seems from its documentation (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) that Object.assign() will 'merge together' all the objects passed into it. In this case, however, todo and {completed: true} are already objects, so I don't see the point of passing an empty object literal, {}, as the first argument to Object.assign(). Can anybody clarify this?
When you use Object.assign, the first object you give it will have all the rest of the objects merged into it. That is to say, the first object will be mutated.
If you want to avoid mutating the objects you're merging, it's helpful to pass in the empty object as the first parameter to prevent any of the component objects from changing.
Here's an example demonstrating the difference:
const obj1 = {
foo: "bar"
}
const obj2 = {
key: "value"
}
// Here, obj1 is the same after the Object.assign call
console.log(Object.assign({}, obj1, obj2));
console.log(obj1)
console.log(obj2)
console.log("\n\n")
// Note that after this call, obj1 holds both keys. So this will mutate it:
console.log(Object.assign(obj1, obj2));
console.log(obj1) // This is different now
console.log(obj2)
If you don't pass an empty object in, the original todo object will be modified. This may be what you want, but more often than not it isn't.
This is due to the way objects are all references, and are not cloned by default.
Short answer: Objects and Arrays are assignment by reference.
In this example, changing one will change the other, they are not immutable:
let x = {param:1}
const foo = (a) => {
a.param +=1;
console.log('response', x, a)
}
foo(x);
To fix that, we use Object.assign()
let x = {param:1}
const foo = (a) => {
let b = Object.assign({}, a);
b.param +=1;
console.log('response', b, x)
}
foo(x);

How to dynamically set value of an object property in reactJS state?

Let's say a component has state such as:
this.state = {
enabled: {
one: false,
two: false,
three: false
}
}
How can this.setState() be used to set the value of a dynamic property?
For instance, this does not work:
let dynamicProperty = "one"
this.setState({
enabled[dynamicProperty]: true
})
However, this does work, but is also bad practice:
this.enabled = {
one: false,
two: false,
three: false
}
let dynamicProperty = "one"
this.enabled[dynamicProperty] = true;
How can this.setState() be used to accomplish the same thing?
You need to create a copy of the original object and only change the property you want to update. The easiest way to do that is to use the object spread operator:
this.setState(currentState => ({enabled: {...currentState.enabled, one: true}}));
or in a more verbose form:
this.setState(currentState => {
const enabled = {...currentState.enabled, one: true};
return {enabled};
});
If the property name is only known at runtime you can do it like this:
const setEnabled = name => {
this.setState(currentState => ({enabled: {...currentState.enabled, [name]: true}}));
};
The standard practice is to copy the the state, modify the copied state, then set state using that clone, like this:
//with spread operator
const enabledClone = {...this.state.enabled};
enabledClone.one = true;
this.setState({enabled : enabledClone});
You can use braces around an object's key to use a variable to determine the key
const dynamicKey = 'one';
const newObj = {[dynamicKey]: true} //equals {one: true}
Since this.setState only merges on toplevel keys, you will have to create a copy of the current enabled object and use the braces notation:
let dynamicProperty = "one"
this.setState({
enabled: {...this.state.enabled, [dynamicProperty]: true}
})

Shallow copy JavaScript object without references

How can I shallowly copy an JavaScript object and get rid of all non-primitive values (all references), while keeping all properties of the given object. Values of the properties might turn to null in this process.
Object.assign, lodash clone and the spread operator allow us to get a shallow copy of an object. However, despite the naming, the result object is not shallow [fact]. It has copied all references too, so the whole object tree is still accessible.
For a analytics solution, I need to get rid of anything deeper than one level.
How can I do it (libraries are also ok, ES6 is fine) without writing dozens of rules to deal with all the possible data types? Ideally, objects and array properties are not lost, but replaced with something, e.g. null or empty objects/arrays.
Example
const source = {
nr: 1,
str: 'ok',
obj: {
uhOh: 'kill me'
},
arr: ['well ok', { uhOh: 'uhOh' }],
}
// apply voodoo
const expected = {
nr: 1,
str: 'ok',
obj: {},
arr: [],
}
// This would also be an valid result:
const expected = {
nr: 1,
str: 'ok',
obj: null,
arr: null,
}
You could loop through the keys of the object using for...in. If the value is an object, set the key to null in expected, else set the value in expected to the value from source
const source = {
nr: 1,
str: 'ok',
obj: {
uhOh: 'kill me'
},
arr: ['well ok', {
uhOh: 'uhOh'
}],
}
const expected = {};
for (const key in source) {
if (typeof source[key] === 'object')
expected[key] = null
else
expected[key] = source[key]
}
console.log(expected)
This is not an answer in it's own right, but an addendum to the excellent answer by #adiga, this time using typescript and a type parameter:
private primitiveClone<T>(source: T): T {
const dto = Object.assign({}, source);
for (const key in dto) {
if (typeof dto[key] === 'object') {
dto[key] = null;
}
}
return dto;
}
usage
var simpleClone = primitiveClone(data);

How do I only update one value of my reducer in Redux?

Below is my code
function customMsg(state, action) {
state = state || {
person: {
isFetching: false,
didInvalidate: false,
name: "",
height: "",
}
};
switch(action.type) {
case ACTION_TYPES.PERSON.FETCH_PERSOn_CONTENT_SUCCESS:
return $.extend({}, state, {
person.name: action.result.name
});
default:
return state;
}
}
How do I only update one value of my reducer in Redux?
Above is a example which i only want to update the name of person object.
How can i do that?
Using jQuery extend, create a clone and set person name:
var newState = $.extend({}, state);
newState.person.name = action.result.name;
return newState;
Otherwise, to clone deeply an object you can use lodash cloneDeep().
Another way is to use immutableJS to set your app state as immutable. It is much more bug "mutable" proof and offers functions to set deep nested value in an immutable. See updateIn:
return state.updateIn(['person', 'name'], () => action.result.name);
Try it!

Categories

Resources