Different outcomes between Object assign and Object spread - javascript

I am using typescript and compiling to ES2016.
I noticed that I am calling two different functions with similar this object in a Function.prototype.call().
I tried to merge both this objects by using a common object which would use ...spread in the beginning of the object like so
let selfShared = {
props,
// ...
};
let selfHost = {
...selfShared,
// ...
};
let selfGuest = {
...selfShared,
// ...
};
The idea of using the spread in the begging was that I could overwrite the shared properties in either of the this objects if I saw it fit.
But unlike when setting props straight in the this objects using the spread gave out weird results, which turned out to be because tsc compiled the code as
let selfShared = {
props
};
let selfHost = Object.assign(Object.assign({}, selfShared), {
// ...
});
// ...
using my code
let state = undefined;
let attributes = {};
let selfShared = {
props: attributes
};
let selfHost = {
...selfHost,
get state() {
console.log("selfHost get state");
return state;
},
set state(originalStates) {
console.log("selfHost set state");
!state ? state = originalStates : console.error("`this.states` already defined in the host function.");
}
}
the output looks like
let state = undefined;
let attributes = {};
let selfShared = {
props: attributes
};
let selfHost = Object.assign(
Object.assign({}, selfShared), {
get state() {
console.log("selfHost get state");
return state;
},
set state(originalStates) {
console.log("selfHost set state");
!state ? state = originalStates : console.error("`this.states` already defined in the host function.");
}
});
now at least on firefox 74 to 77 inserting both of the codes into the console and adding
// ...
selfHost.state = {
thing: "some"
};
selfHost.state = {
some: "thing"
};
throws out different logs...
The precompiled code gives me two of set state and an error which are the expected outputs, but the compiled code gives me a get state and ignores the rule in set state outputting
{
some: "thing"
}
instead of the expected
{
thing: "some"
}
as in the precompiled code?
Setting the spread into the bottom of the file compiles to
let selfHost = Object.assign({
get state() {
console.log("selfHost get state");
return state;
},
set state(originalStates) {
console.log("selfHost set state");
!state ? state = originalStates : console.error("`this.states` already defined in the host function.");
}
}, selfShared);
which gives the right output but doesn't allow me to overwrite the properties given by selfShared.
Can you explain why this happens with Object.assign and if there is a trick to get an output from tsc that still lets me do what I originally wanted?

When using spread
let obj = {
...otherObj,
// ...
}
or
let obj = Object.assign({}, otherObj, {
// ...
})
The spread is interpreted as literal which means that as in the polyfill the properties are read as strict values, which means that normal values are read normally, setters are ignored and getters are read as normal values.
Setters and getters work as written in the question when the spread is written in the end as
let obj = {
// ...
...otherObj
}
or
let obj = Object.assign({
// ...
}, otherObj)
since otherObj only extends the unique objects.

Related

Attempted to assign to readonly property

first of all i get my redux array then in my_function copy that into new variable like below :
let transactions_list = useSelector(state => state.transactions_list.value);
let new_transactions_list = [...transactions_list];
when i want to change my new_transactions_list very deeply i got the error
const my_function = () => {
let new_transactions_list = [...transactions_list];
new_transactions_list[yearIndex].data_yearly[monthIndex].data_monthly.push(new_obj);
}
but when i define an array in class(without redux), it's work
Even if you are using the spreading [...transactions_list], you are still only copying the first level of the array, which means that the object below that array is still the same one that redux uses.
You have 2 options:
This is how redux recommends you to update nested object link
function updateVeryNestedField(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue
}
}
}
}
}
Or you can use something like immer, which will allow you to update your object even with immutable like this
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
})
Either way, you will have to update your redux state afterward since this change will only be local in your code and not the global redux.

How does error value get assigned in React's AJAX and API example?

In React Docs - AJAX and APIs there is an example where a JSON object is created with a key that has no value.
I believe I am missing some fundamental understanding of JavaScript objects. What value is given to the error key in the following snippet and how does this value get there?
(error) => {
this.setState({
isLoaded: true,
error
})
}
Later, when the state is rendered, the value of error is assumed to have some message property. I have run the example code and it clearly works, but I am stuck trying to explain to another person how it works exactly.
if(error) {
return <div>Error: {error.message}</div>
It's object property shorthand ,
basically if you have a variable with the same name as the key, you can do :
const key = "someValue";
const obj = { key };
// instead of
const obj = { key : key };
const name = "John";
const age = 30;
const obj = {
name,
age
}
console.log(obj);
In the example you provided, error is an object having message inside it, something like :
const error = {
message: "some message",
// ...
}

Javascript, in a React application assign to {} in a function component, code review

I have this code in a friend of mine React application and I need to understand what this code does explicitly:
const Component = ()=> (
<QueryFetcher>
{({ data }) => {
const { user: { profile = {} } = {} } = data
return (
<div>
{profile.username && profile.username}
</div>
)
}}
</QueryFetcher>
)
What is this line for?
const { user: { profile = {} } = {} } = data
Is it correct to assign something to {} using { user: { profile = {} } = {} } in this functional component? Or in a render() hook of a stateful component in React?
const { user: { profile = {} } = {} } = data basically means that your retrieving the user profile.
const means that you are creating a new variable
{ user: { profile } } } means that you are retrieving profile inside of user
= {} means that if the object is undefined, use an empty object so it will not fail because doing user.profile will throw an error if user is undefined.
= data means that you retrieving this info from the data variable
So, this line means, from the variable data, go take the user, if the user is undefined, use an empty object. Then, go take the profile, if the profile is undefined, use an empty object. Then create a variable called profile with the result. This is like doing this:
const user = data.user === undefined ? {} : data.user;
const profile = user.profile === undefined ? {} : user.profile;
What is this line for?
const { user: { profile = {} } = {} } = data
It's basically just chained ES6 object-destructuring with default values.
What this line does in words:
Read "user" from "data", if "user" is undefined, assign {} as a default value
Read "profile" from "user", if "profile" is undefined, assign {} as a default value
Is it correct
It is mostly a short-hand syntax used to remove repetitive stuff. So instead of accessing multiple object props separately e.g.
this.props.prop1, this.props.prop2, ...
you can use
const { prop1, prop2 } = this.props;
It also helps other readers later quickly understanding what variables are used in a method if all necessary props are destructured at the start.

Issue in Typing a class with Flowjs

I have the following code that I'm attempting to type with Flow
type Metadata = {
currentPage: string,
};
type State = {
result: {
metadata: Metadata,
}
}
type EmptyObject = {};
type Test = Metadata | EmptyObject;
class HelperFn {
state: State;
metadata: Test;
constructor(state: State) {
this.state = state;
if (state && state.result && state.result.metadata) {
this.metadata = state.result.metadata;
} else {
this.metadata = {};
}
}
getCurrentPageNumber() {
return this.metadata.currentPage;
}
}
I've created Types that I'm assigning later on. In my class, I assign the type Test to metadata. Metadata can be either an object with properties or an empty object. When declaring the function getCurrentPageNumber, the linter Flow tells me that it
cannot get 'this.metadata.currentPage' because property 'currentPage' is missing in EmptyObject
Looks like Flow only refers to the emptyObject. What is the correct syntax to tell Flow that my object can either be with properties or just empty?
Since metaData can be empty, Flow is correctly telling you that this.metadata.currentPage may not exist. You could wrap it in some sort of check like
if (this.metadata.currentPage) {
return this.metadata.currentPage
} else {
return 0;
}
To get it to work properly.

React to nested state change in Angular and NgRx

Please consider the example below
// Example state
let exampleState = {
counter: 0;
modules: {
authentication: Object,
geotools: Object
};
};
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
}
Here in the MyAppComponent we react on changes that occur to the counter property of the state. But what if we want to react on nested properties of the state, for example modules.geotools? Seems like there should be a possibility to call a store.select('modules.geotools'), as putting everything on the first level of the global state seems not to be good for overall state structure.
Update
The answer by #cartant is surely correct, but the NgRx version that is used in the Angular 5 requires a little bit different way of state querying. The idea is that we can not just provide the key to the store.select() call, we need to provide a function that returns the specific state branch. Let us call it the stateGetter and write it to accept any number of arguments (i.e. depth of querying).
// The stateGetter implementation
const getUnderlyingProperty = (currentStateLevel, properties: Array<any>) => {
if (properties.length === 0) {
throw 'Unable to get the underlying property';
} else if (properties.length === 1) {
const key = properties.shift();
return currentStateLevel[key];
} else {
const key = properties.shift();
return getUnderlyingProperty(currentStateLevel[key], properties);
}
}
export const stateGetter = (...args) => {
return (state: AppState) => {
let argsCopy = args.slice();
return getUnderlyingProperty(state['state'], argsCopy);
};
};
// Using the stateGetter
...
store.select(storeGetter('root', 'bigbranch', 'mediumbranch', 'smallbranch', 'leaf')).subscribe(data => {});
...
select takes nested keys as separate strings, so your select call should be:
store.select('modules', 'geotools')

Categories

Resources