I have problems with Object.assign and ... spread operator. I need to process values (object with name and value tha are objects).
Example my values object:
{
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
otherfields: "{
country: 'ZW',
city: 'Zurick'
}"
}
otherfields comes from graphql , so it's string, i must convert to object.
With my process I look for this result:
{
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
country: 'ZW',
city: 'Zurick'
}
The code have more code that I paste here, there is a lot of controls for values and conversion but mainly, the idea is reassing values,
With these two case assign to the same variable is not working:
Case 1, with object.assign
processValues = (values)=>
let newValues = {...values}; //
for (const fieldName in Tables[table].fields) {
let value = values[fieldName];
value = JSON.parse(value);
newValues = { ...newValues, ...value};
console.error('after mix',newValues);
Case 2, with object.assign
processValues = (values)=>
let newValues = Object.assign({}, values}; //
for (const fieldName in Tables[table].fields) {
let value = values[fieldName];
value = JSON.parse(value);
newValues = Object.assign( newValues, value};
console.error('after mix',newValues);
How it's works, when I use a new variable, by example:
newValues2 = Object.assign( newValues, value};
but my idea is not use another variable because , i need to get values and set values for the original variable 'newValues' , if I use another variable the code would be more cumbersome.
I'm using in a project with create-react-app. I don't know if it's a problem with babel, because Object.assign and spread operator are not inmmutable; or yes ?
INFO:
Tables[table].fields is a object with definition por my table structure, there therea lot of rules, but basically i need to know why object and ... does not work
The use of JSON.stringify will not help, as this will produce a JSON string, which will have an entirely different behaviour when spreading it (you get the individual characters of that string).
Here is how you can achieve the result with "otherfields" as the special field (you can add other fields in the array I have used):
const processValues = values =>
Object.assign({}, ...Object.entries(values).map( ([key, val]) =>
["otherfields"].includes(key) ? val : { [key]: val }
));
// Example:
const values = {
id: "12",
name: "Hotel MESSI",
email: "myemail#aol.com",
phone: "+001060666661",
otherfields: {
country: 'ZW',
city: 'Zurick'
}
};
const result = processValues(values);
console.log(result);
The first argument to assign is the target. So it's going to get changed. You can simply pass an empty object for your target if you don't want any of the sources to change.
When you are using first argument as {} then no value will change.
For more please refer it.
https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/
Related
const [data , setData] = ({firstname:'' , lastname:'' , password:''});
const handleChange = (e) => {
setData({...data , [e.target.name] : e.target.value })
I am new to react and java script and can't figure out why ...data(for destructuring) is used is handlechange() function and what is the use of [] braces in [e.target.name] : e.target.value
In this case, ...data (destructuring) is being used so that other attributes already present in data are not affected by the change to the attribute that triggered the event that is actually calling handleChange().
For example, suppose you have a form that has the fields: "Name", "Age", and "Profession". Suppose you fill the form fields in the same above order. As you fill each form field and handleChange() is called your data object gets updated:
When filling the name:
{ name: "John" }
When filling age:
{ name: "John", age: "20" }
When filling profession:
{ name: "John", age: "20", profession: "student" }
...data (destructuring) is a way to ensure the previous attributes are preserved as you add or update new attributes to the data object.
For the second part of your question, the square brackets are used to ensure javascript will evaluate e.target.name and use its value as the attribute to be added or updated to data. The form input fields for this example, should have the name attributes as "name", "age", and "profession", respectively.
Here is the "long" version of the handleChange function. I think it will help you understand the function mission easier.
cons handleChange = (e) => {
const currentData = data;
currentData[e.target.name] = e.target.value;
setData(currentData)
}
The first is the three dots with an object like ...data. It's almost the same with Object.assign. In this case, it help you create a new object from the value of data.
const data = {firstname:'' , lastname:'' , password:''};
const newData = {...data}; // {firstname:'' , lastname:'' , password:''}
The second is the [] braces. If you want to set a property key from a variable inside the {}. You may use the [] braces to wrap this variable.
const key = 'name';
const a = {[key]: 'John'} // {name: 'John'}.
This is not really related to React, you may get the function like this in other places. Some articles you may read about them:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
I'm trying to render a dynamic list of fields from a JSON file.
Some fields have to go through this accountFieldMap object I created for key renaming purposes.
For example it finds the key userFirstName1 from the JSON and renders the value of it as firstName at the component.
const accountFieldMap = {
firstName: "userFirstName1",
lastName: "userLastName1",
ID: "userID",
location: `userLocation.city`,
};
The only issue is with the location field.
How can I let JavaScript know that it should render that city nested field and show it as location?
If I understand you correctly, location.city is a path to some value in object.
There are some libraries for this like lodash, which have inbuilt functions that can resolve that, but if you want to do it in vanilla js, you can do it by splitting this string by dot and going through that array to get a value.
const getByPath = (path, obj) => {
const splittedPath = path.split(".");
return splittedPath.reduce((acc, curr) => {
acc = obj[curr];
return acc;
}, obj)
}
So in this case if you have object like
const testObj = {
location: {city: "Kyiv"},
firstName: "Oleg"
}
It will return you "Kyiv" if you will pass into getByPath "location.city" as path. And it will also work in case if there is no nesting, so
getByPath("firstName", testObj)
will return you "Oleg"
you only have to map the array and create a new object;
import fileData from "../path/to/json";
const people = fileData.arrayName.map(person => ({
firstName: person.userFirstName1,
lastName: person.userLastName1,
ID: person.userID,
location: person.userLocation.city,
}));
The following code demonstrates an issue that has surfaced while writing an app using the Vue framework for the front end. The issue is really a JS one though.
Here is the data object needed by the Vue component:
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
and here is an object containing a row of data from the database:
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
It is necessary to copy some of the values from retrieved into data. The current way of doing the copy is the following:
data.accountId = retrieved.AccountID;
data.prospectId = retrieved.ProspectID;
data.address = retrieved.Address;
data.city = retrieved.City;
data.state = retrieved.State;
console.log('data', data);
The result of the above code is the desired outcome and it looks like this:
I'm looking for a more efficient way to do the copying because it's tedious when there are many key/value pairs involved.
I've tried this:
data = { ...data, ...retrieved };
console.log('data', data);
which results in this
which basically unions all the key/value pairs together. Not the desired outcome.
It is critical that the key names in data keep their exact names and no extra key/value pairs get added to data. How can this be achieved?
Since the capitalization is different, spread won't work. You'll have to iterate over an array mapping the properties on the different objects to each other:
const propsToCopy = {
// data // retrieved
accountId: 'AccountID',
prospectId: 'ProspectID',
// ...
};
for (const [dataProp, retrievedProp] of Object.entries(propsToCopy)) {
data[dataProp] = retrieved[retrievedProp];
}
That said, having slightly different property names for the same data like this seems very strange, since it makes the code a lot more convoluted than it needs to be and greatly increases the risk of typo-based problems, when a property is capitalized but doesn't need to be, or vice-versa. Consider if you can use just a single property name format instead, if at all possible; then the propsToCopy could be reduced to an array:
const propsToCopy = ['accountId', 'prospectId', /* ... */ ];
You can use a Proxy in order to intercept all settings of values. This allows only setting known values and ignoring anything else.
To make the setting of the property preserve the case, we can just lookup the original key case-insensitively and use the original one.
Finally, using Object.assign() will call all the setters on the target which means that the proxy can intercept these calls:
const eqCaseInsensitive = a => b =>
a.toLowerCase() === b.toLowerCase();
const handler = {
set(target, prop, value, receiver) {
const key = Object.keys(target)
.find(eqCaseInsensitive(prop));
if (key !== undefined) {
return Reflect.set(target, key, value, receiver);
}
return true;
}
}
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
Object.assign(new Proxy(data, handler), retrieved);
console.log(data);
This can further be converted to a helper function that is analogous to Object.assign() and allow for as many sources as you wish. To save some processing time, there is no need to do a full search for each property assignment - a simple lookup map can be precomputed that holds lowercase property names as keys and normal case property names as values:
const assignOnlyKnownProps = (target, ...sources) => {
const known = new Map(
Object.keys(target)
.map(key => [key.toLowerCase(), key])
);
const handler = {
set(target, prop, value, receiver) {
const lookup = prop.toLowerCase();
if (known.has(lookup)) {
Reflect.set(target, known.get(lookup), value, receiver);
}
return true;
}
}
return Object.assign(new Proxy(target, handler), ...sources);
}
let data = { accountId: '', prospectId: '', address: '', city: '', state: '' }
const retrieved = {
"ProspectID": "4",
"AccountID": "1003",
"Address": "E2828 Highway 14",
"City": "Madison",
"State": "WI",
"Created": "2021-02-27 11:49:33.523",
"Updated": "2021-02-27 11:49:33.523"
}
assignOnlyKnownProps(data, retrieved);
console.log(data);
Creating a copy of retrieved and converting all the key names to lowercase, then populating the values for data works -
const r = Object.fromEntries(
Object.entries(retrieved).map(([k, v]) => [k.toLowerCase(), v])
);
// Object.keys(data).forEach(key => {
// data[key] = r[key.toLowerCase()];
// });
Object.keys(data).map(k => data[k] = r[k.toLowerCase()]);
console.log('r', r);
console.log('data', data);
I want to convert an object containing keys and values into a string of query, something like:
obj1: {
abc: "Abc",
id: 1,
address: "something"
}.
however this object is dynamically created, hence the number and the keys in it may vary, like another obj dynamically could be;
obj1: {
test: "123",
test2: "3333"
}
whatever object does the server return I need to convert this into a string of query/; like
query1 = "test:'123'and test2: '3333'"
query2 = "abc:'Abc' and id: 1 and address: 'something'"
I could simpley try:
Object.keys(obj1)[0]
Object.keys(obj1)[1]
Object.keys(obj1)[2]
and get the keys and use:
Object.keys(obj1)[0]: obj1[Object.keys(obj1)[0]]
this would give me:
abc:'Abc'
however since the length of the keys in the object is not static and nor are the keys I'm finding it difficult to know how to concatenate these values into one string out of this
any idea?
There are some minor inconsistencies in your format, but disregarding those you can use Object.entries, Array#map and Array#join as follows:
const objToQuery = o =>
Object.entries(o).map(([k, v]) =>
`${k}: ` + (typeof v === "number" ? v : `'${v}'`)
).join(" and ")
;
const obj1 = {
abc: "Abc",
id: 1,
address: "something"
};
const obj2 = {
test: "123",
test2: "3333"
};
console.log(objToQuery(obj1));
console.log(objToQuery(obj2));
Just being a bit creative here and using built in URLSearchParams() then replacing the = and the &. Not sure how important your quotes are as the other solution is more efficient if they are needed
const obj1 = {
abc: "Abc",
id: 1,
address: "something"
}
const objToQuery = o => {
return (new URLSearchParams(Object.entries(o)))
.toString().replace(/=/g, ': ').replace(/&/g, ' and ')
}
console.log(objToQuery(obj1))
let schools = [
{ name: "Yorktown"},
{ name: "Stratford" },
{ name: "Washington & Lee"},
{ name: "Wakefield"}
]
let updatedSchools = editName("Stratford", "HB Woodlawn", schools)
console.log( updatedSchools[1] ) // { name: "HB Woodlawn" }
const editName = (oldName, name, arr) =>
arr.map(item => {
if (item.name === oldName) {
// what is happening below!?
return {
...item,
name
}
} else {
return item
}
})
first of all, i'm sorry if this question might be easy for you, but i'm having trouble understanding how the return statement of the snippet works and would really appreciate help.
return { ...item, name }
So i would expect updatedSchool to be (even though it's invalid syntax):
[
{name: "Yorktown"},
{ name: "Yorktown", "HB Woodlawn"},
{ name: "Washington & Lee"},
{ name: "Wakefield"}
]
why does it produce { name: "HB Woodlawn" }?
Simply desugar expression step by step
{...item, name }
First {name} is shortcut for {name: name}
Then {...obj} is the same as Object.assign({}, obj)
Combining both gives Object.assign({}, obj, {name: name})
Given obj = {name: 'Stratford'} has only one property name it will simply create new object and replace name with a new one.
You can read about Object.assign here
return { // the spread operator assigns existing properties of item
...item, // to the new returned object
name // similar to return Object.assign(item, {name: name})
}
The rest parameter can work on objects as well as arrays in browsers that support it. If you want to understand the code, it's best to walk through it.
editSchools is a function that takes an oldName, a name, and an array. It returns the result of the mapping from array to a new array. Each element in the new array is determined by the callback function that map executes. If the item's name property is equal to the oldName, then a new object is created which will take its place, {...item, name}. This is where the confusion lies.
It does something weird. The new object recieves all the keys of the item object, and then it will define (or redefine) the name property to the value of name provided to editSchools.
So in essence, this code finds the objects that have a name key whose value is oldName and replaces it with an identical new object with a changed name property to the new name value.