This question already has answers here:
Why does a js map on an array modify the original array?
(5 answers)
Updating an array of objects without mutation
(1 answer)
Why does map mutate array of objects?
(3 answers)
Closed 3 years ago.
I have a structure like this:
let MainItem = [
{
"Id": "1",
"Cost": "1000"
},
{
"Id": "2",
"Cost": "5000"
},
{
"Id": "3",
"Cost": "2000"
},
{
"Id": "4",
"Cost": "3000"
}
];
I am going to change the value of Cost each of the elements with map() loop and store it in NewMainItem.
let NewMainItem = MainItem.map((item, i) => {
item.cost = item.cost + 1000
return item
});
console.log(MainItem)
console.log(NewMainItem)
The main problem is that by changing Cost in NewMainItem, the value of
Cost in MainItem will be changed too, but I don not want to do this. By using map() loop why the main object (MainItem ) will be changed too?
You should use map to create new objects from the existing ones, not modify them.
You could use the spread operator to create a new item like this:
{ ...item, Cost: parseFloat(item.Cost) + 100 };
Here parseFloat was used because the Costs values are set as string in your snippet. Feel free to change this as it fits your needs.
See below snippet:
let MainItem = [
{
"Id": "1",
"Cost": "1000"
},
{
"Id": "2",
"Cost": "5000"
},
{
"Id": "3",
"Cost": "2000"
},
{
"Id": "4",
"Cost": "3000"
}
];
let NewMainItem = MainItem.map((item, i) => {
return { ...item, Cost: parseFloat(item.Cost) + 100 };
});
console.log(MainItem)
console.log(NewMainItem)
You could assign a new object with the wanted changed value.
var mainItem = [{ Id: "1", Cost: "1000" }, { Id: "2", Cost: "5000" }, { Id: "3", Cost: "2000" }, { Id: "4", Cost: "3000" }],
newMainItem = mainItem.map(item => Object.assign({}, item, { Cost: +item.Cost + 1000 }));
console.log(mainItem);
console.log(newMainItem);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
I have the following array
Array["MyArray",
{
"isLoaded":true,
"items":
[{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}],
"coupons":null
}
]
I need to load product names and their quantity from the array.
const result = [key, value].map((item) => `${item.name} x ${item.quantity}`);
Here's one possible way to achieve the desired result:
const getProductsAndQuantity = ([k , v] = arr) => (
v.items.map(it => `${it.name} x ${it.quantity}`)
);
How to use it within the context of the question?
localforage.iterate(function(value, key, iterationNumber) {
console.log([key, value]);
const val2 = JSON.parse(value);
if (val2 && val2.items && val2.items.length > 0) {
console.log(val2.items.map(it => `${it.name} x ${it.quantity}`).join(', '))
};
});
How it works?
Among the parameters listed in the question ie, value, key, iterationNumber, only value is required.
The above method accepts the key-value pair as an array (of 2 elements) closely matching the console.log([key, value]); in the question
It uses only v (which is an object). On v, it accesses the prop named items and this items is an Array.
Next, .map is used to iterate through the Array and return each product's name and quantity in the desired/expected format.
Test it out on code-snippet:
const arr = [
"MyArray",
{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}
];
const getProductsAndQuantity = ([k, v] = arr) => (
v.items.map(
it => `${it.name} x ${it.quantity}`
)
);
console.log(getProductsAndQuantity());
I understood. You should learn about array methods such as map, filter, reduce. Here you go...
const items = [{
"id":"4",
"name":"ProductA",
"manufacturer":"BrandA",
"quantity":1,
"price":"25"
},{
"id":"1",
"name":"ProductB",
"manufacturer":"BrandB",
"quantity":5,
"price":"20"
}];
const result = items.map((item) => `${item.name} x ${item.quantity}`);
console.log(result);
I think I understand the question to say that the input is an array of objects, each containing an array of items. The key is that a nested array requires a nested loop. So, we iterate the objects and their internal items (see the lines commented //outer loop and // inner loop below)
Also, half-guessing from the context, it looks like the that the OP aims to assemble a sort of invoice for each object. First a demo of that, (and see below for the version simplified to exactly what the OP asks)...
const addInvoice = obj => {
let total = 0;
// inner loop
obj.invoice = obj.items.map(i => {
let subtotal = i.quantity * i.price;
total += subtotal
return `name: ${i.name}, qty: ${i.quantity}, unit price: ${i.price}, subtotal: ${subtotal}`
});
obj.invoice.push(`invoice total: ${total}`);
}
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
// outer loop
objects.forEach(addInvoice);
console.log(objects);
If my guess about the goal went to far, just remove the unit price, subtotal and total lines from the invoice function...
const objects = [{
"isLoaded": true,
"items": [{
"id": "4",
"name": "ProductA",
"manufacturer": "BrandA",
"quantity": 1,
"price": "25"
}, {
"id": "1",
"name": "ProductB",
"manufacturer": "BrandB",
"quantity": 5,
"price": "20"
}],
"coupons": null
}]
const summaryString = obj => {
return obj.items.map(i => `${i.name}, ${i.quantity}`);
}
const strings = objects.map(summaryString);
console.log(strings);
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I have 2 object in array
dataV[1] = {
"resolution": "4"
};
datas[1] = {
{qty_approved: "1", resolution: "5", status: "", order: "1332"}
}
if both index same then i want to update with the value from 1st. I want to update resolution value to 4 from 5. Based on upcoming value from new array value in dataV
Expected output like this :
datas[1] = {
{qty_auth: "1", resolution: "4", status: "", order: "1332"}
}
Wherever you're updating the value of dataV you can do something like this, to update the datas:
datas = datas.map((data, i) => {
return { ...data, resolution: dataV[i].resolution };
});
And if you're using react you can do the same thing in useEffect with dataV as a dependency. So, whenever dataV changes, datas will change automatically.
let array1 = [
{
resolution: '4',
},
];
let array2 = [
{ qty_approved: '1', resolution: '5', status: '', order: '1332' },
];
let array3 = array1.map((element, index) => {
if (typeof array2[index] != 'undefined') {
return { ...array2[index], ...element};
}
return element;
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
Since you can get the corresponding element by array index, you can get the corresponding resolution as easy as the following snippet:
datas.forEach( (_, i) => {
datas[i].resolution = dataV[i].resolution
})
Using Array#reduce, iterate over dataV while updating a Map where the index is the key and the resolution is the value
Using Array#forEach, iterate over datas, if the above map has a key of such index, update the resolution
const
dataV = [ { "resolution": "4" }, { "resolution": "7" }, { "resolution": "1" } ],
datas = [
{ qty_approved: "1", resolution: "5", status: "", order: "1332" },
{ qty_approved: "1", resolution: "3", status: "", order: "1331" },
{ qty_approved: "1", resolution: "9", status: "", order: "1333" },
];
const indexResolutionMap = dataV.reduce((map, { resolution }, i) =>
map.set(i, resolution)
, new Map);
datas.forEach((e, i) => {
const resolution = indexResolutionMap.get(i);
if(resolution) e.resolution = resolution;
});
console.log(datas);
The following approach is going to map each item of datas. The mapping function nevertheless will be a real function (not an arrow function), thus it is aware of map's second thisArg argument which for the OP's use case will be the dataV array where one wants to read the resolution values from.
The advantage of such an approach is, that one can work with a reusable mapper function which is agnostic to outside array references because it does process an array while the related/corresponding array gets provided as the mapper functions this context.
The mapping functionality itself does try to not mutate the original item reference by loosely decoupling the reference via creating a shallow copy of the entire item. On top the resolution value which was read from the corresponding array via this[idx].resolution gets assigned to the just created shallow copy.
const datas = [
{ qty_approved: "1", resolution: "5", status: "", order: "1332" },
{ qty_approved: "1", resolution: "3", status: "", order: "1331" },
{ qty_approved: "1", resolution: "9", status: "", order: "1333" },
];
const dataV = [{
"resolution": "4",
}, {
"resolution": "7",
}, {
"resolution": "1",
}];
// mapping approach.
function copyItemAndAssignSameIndexResolutionFromTargetArray(item, idx) {
// - this merge pattern is agnostic about an `item`'s structure.
// - `item` could feature more keys but just `resolution` gets reassigned.
return Object.assign({}, item, { resolution: this[idx].resolution });
}
console.log(
'mapped version of changed `dataV` items ...',
datas.map(copyItemAndAssignSameIndexResolutionFromTargetArray, dataV)
);
console.log('still unmutated `datas` ...', { datas });
.as-console-wrapper { min-height: 100%!important; top: 0; }
In case the OP wants to mutate each of the datas array's items, the above introduced mapper function changes slightly in order to be utilized by a context aware forEach ...
const datas = [
{ qty_approved: "1", resolution: "5", status: "", order: "1332" },
{ qty_approved: "1", resolution: "3", status: "", order: "1331" },
{ qty_approved: "1", resolution: "9", status: "", order: "1333" },
];
const dataV = [{
"resolution": "4",
}, {
"resolution": "7",
}, {
"resolution": "1",
}];
// mapping approach.
function assignSameIndexResolutionFromTargetArray(item, idx) {
item.resolution = this[idx].resolution;
// Object.assign(item, { resolution: this[idx].resolution });
}
datas.forEach(assignSameIndexResolutionFromTargetArray, dataV);
console.log('mutated `datas` array ...', { datas });
.as-console-wrapper { min-height: 100%!important; top: 0; }
Or (desperately guessing) is the OP looking for a solution where a datas item has to be updated/mutated by an explicitly provided dataV item? Something like this?..
const datas = [
{ qty_approved: "1", resolution: "3", status: "", order: "1331" },
{ qty_approved: "1", resolution: "5", status: "", order: "1332" },
{ qty_approved: "1", resolution: "9", status: "", order: "1333" },
];
const dataV = [{
"resolution": "1",
}, {
"resolution": "4",
}, {
"resolution": "7",
}, ];
function updateResolution(targetArray, sourceArray, resolutionItem) {
const updateIndex =
sourceArray.findIndex(item =>
item.resolution === resolutionItem.resolution
);
targetArray[updateIndex].resolution = resolutionItem.resolution;
return targetArray[updateIndex];
}
console.log(
'updateResolution(datas, dataV, { resolution: "4"}) ...',
updateResolution(datas, dataV, { resolution: "4"})
);
console.log('mutated `datas` array ...', { datas });
console.log(
'updateResolution(datas, dataV, { resolution: "7"}) ...',
updateResolution(datas, dataV, { resolution: "7"})
);
console.log('mutated `datas` array ...', { datas });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have two arrays:
const array1 = [{
"id": "4521",
"name": "Tiruchirapalli",
"stateId": "101"
},
{
"id": "1850",
"name": "Tenkasi",
"stateId": "101"
},
{
"id": "202",
"name": "Thanjavur",
"stateId": "101"
},
{
"id": "505",
"name": "Ernakulam",
"stateId": "102"
},
];
And now array2
const array2 = [{
"id": 1850,
"cityName": "Tenkasi",
"aliasNames": [
"Thenkasi"
]
},
{
"id": 4521,
"cityName": "Tiruchirapalli",
"aliasNames": [
"Trichy"
]
},
{
"id": 202,
"cityName": "Thanjavur",
"aliasNames": [
"Tanjore"
]
},
{
"id": 505,
"cityName": "Ernakulam",
"aliasNames": [
"Kochi",
"Cochin"
]
},
];
what i need to do is, how to filter both the arrays at same time ( or filter first one and then second which ever one is performance effective ).
For instance, when user types "Kochi", first it should check on array1 to find if its has name="Kochi", if it has then we can set the state with that and if it doesnt have we need to find it on array2 and the update the state !
Which is fast and effective way to handle this - ( array1 has 2500 records and array2 has 990 records ) so performance / speed is also a concern
My attempt:
searchFilterFunction = text => {
this.setState({ typedText: text });
const newData = array1.filter(item => {
const itemData = `${item.name.toUpperCase()}`;
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
this.setState({ data: newData});
};
How to implement the second filter in optimized way ?
For instance, when user types "Kochi", first it should check on array1
to find if its has name="Kochi", if it has then we can set the state
with that and if it doesnt have we need to find it on array2 and the
update the state !
I would do something like this with Array.find.
if( array1.find(item=>item.name.toUpperCase() === text) ) {
// set state
} else if( array2.find(item=>item.cityName.toUpperCase() === text) ) {
// set state
}
A refined form would be
let result = array1.find(item=>item.name.toUpperCase() === text);
// check in array 2 as we cannot find in array 1
if(!result) {
result = array2.find(item=>{
// check in aliasNames and in cityName
return item.cityName.toUpperCase() === text || item.aliasNames.includes(text);
}
);
}
if(result) {
setState(result);
} else {
// place not found
}
Regarding the performance based on your array count you will not see much difference. If you want to save some milliseconds you can check the array with least count first as mentioned in one of the comments. But the time also varies based on were the element is in array.
I think this is the most optimal solution because nesting the two filter won't work as you need to filter from first array and then second.
const array1 = [{
"id": "4521",
"name": "Tiruchirapalli",
"stateId": "101"
},
{
"id": "1850",
"name": "Tenkasi",
"stateId": "101"
},
{
"id": "202",
"name": "Thanjavur",
"stateId": "101"
},
{
"id": "505",
"name": "Ernakulam",
"stateId": "102"
},
];
const array2 = [{ "id": 1850, "cityName": "Tenkasi",
"aliasNames": [
"Thenkasi"
]
},{"id": 4521,"cityName": "Tiruchirapalli",
"aliasNames": [
"Trichy"
]
},
{
"id": 202,
"cityName": "Thanjavur",
"aliasNames": [
"Tanjore"
]
},
{
"id": 505,
"cityName": "Ernakulam",
"aliasNames": [
"Kochi",
"Cochin"
]
},
];
function filter(text) {
// Complexity Linear
const filter_array = array1.filter((a) => {
return (a.name === text)
});
if (filter_array.length > 0) {
//Set State and return
}
//Complexity Linear and includes complexity Linear O(sq(m*n)) where n is //the aliasName record
const filter_array2 = array2.filter((a) => {
return a.cityName === text || a.aliasNames.includes(text);
});
return filter_array2 //Set State filter array 2
}
console.log(filter("Kochi"));
I'm working on a single page application that connected to a restful web service on Reactjs my problem is not related to Reactjs it's just about a design pattern that should implemented correctly on Javascript.
in a part of project i send a GET request to server and take back a Json response that contains information about addresses.
Above there is complete response of my request :
{
"Result": "Done",
"Errors": null,
"Model": {
"UserAddresses": [{
"AddressId": 121,
"AddressName": "",
"AreaId": 1
}, {
"AddressId": 122,
"AddressName": "",
"AreaId": 1
}, {
"AddressId": 123,
"AddressName": "",
"AreaId": 1
}, {
"AddressId": 124,
"AddressName": "",
"AreaId": 1
}],
"AvailableAreas": [{
"AreaId": 1,
"AreaName": "داخل شهر",
"Cost": 0
}, {
"AreaId": 2,
"AreaName": "شهرک عباس آباد",
"Cost": 5000
}, {
"AreaId": 3,
"AreaName": "ویرمونی",
"Cost": null
}, {
"AreaId": 6,
"AreaName": "راه آهن",
"Cost": null
}]
}
}
As you can see there are two arrays in json above UserAddresses and AvalibleAddresses so what do i want to do is i want to bind this to arrays to their own classes i implemented a class name Address that includes both of these to array :
export default class Address {
constructor(avalibleAreas, userAddresses) {
this._avalibleAreas = avalibleAreas;
this._userAddresses = userAddresses;
}
get avalibleAddresses() {
return this._avalibleAreas;
}
set avalibleAddresses(avalibleAreas) {
this._avalibleAreas = avalibleAreas;
}
get userAddresses() {
return this._userAddresses;
}
set userAddresses(userAddresses) {
this._userAddresses = userAddresses;
}
}
So after getting response i used that class in my controller like this:
let address = new Address(responseAsjson.Model.AvailableAreas,responseAsjson.Model.UserAddresses)
console.log(address)
And as you can see i passed that two arrays as params to constructor then the result of console.log(address) is:
Address {_avalibleAreas: Array(4), _userAddresses: Array(4)}
_avalibleAreas: Array(4)
0: {AreaId: 1, AreaName: "داخل شهر", Cost: 0}
1: {AreaId: 2, AreaName: "شهرک عباس آباد", Cost: 5000}
2: {AreaId: 3, AreaName: "ویرمونی", Cost: null}
3: {AreaId: 6, AreaName: "راه آهن", Cost: null}
_userAddresses: Array(4)
0: {AddressId: 121, AddressName: "", AreaId: 1}
1: {AddressId: 122, AddressName: "", AreaId: 1}
2: {AddressId: 123, AddressName: "", AreaId: 1}
3: {AddressId: 124, AddressName: "", AreaId: 1}
Now the problem is i want to have UserAddresses and AvalibleAreas in specific classes but i dont know how i can do this and for example have AreaId AreaName and Cost as property of the AvalibleAreas Class.
I don't know exactly what you mean by "in specific classes", but you could create two additional classes like this:
class UserAddress {
constructor(addressId, addressName, areaId) {
this._addressId = addressId;
this._addressName = addressName;
this._areaId = areaId;
}
// place your getters and setters here
}
class AvailableArea {
constructor(areaId, areaName, cost) {
this._areaId = areaId;
this._areaName = areaName;
this._cost = cost;
}
// place your getters and setters here
}
And then you replace your old constructor in the Address class by this one:
constructor(availableAreas, userAddresses) {
let a = [];
let b = [];
for (let i = 0; i < availableAreas.length; i++) {
a.push(new AvailableArea(availableAreas[i].AreaId, availableAreas[i].AreaName, availableAreas[i].Cost));
}
for (let i = 0; i < userAddresses.length; i++) {
b.push(new UserAddress(userAddresses[i].AddressId, userAddresses[i].AddressName, userAddresses[i].AreaId));
}
this._availableAreas = a;
this._userAddresses = b;
}
Also: not related to the question, but avalible is meant to be availible, I think. That might save you some debugging time.
First Array
const firstArray = [
{
"value": "306",
"label": "All"
},
{
"value": "316",
"label": "Tips"
},
{
"value": "315",
"label": "News"
},
{
"value": "32",
"label": "Jobs"
}]
Second Array
const secondArray = [
{
name: "name",
description: "desc",
image: "path_image",
culture: [{
name: "name",
value: "32"
}]
},
{
name: "name",
description: "desc",
image: "path_image",
culture: [{
name: "name",
value: "32"
}]
}];
Trying to filter my firstArray with only keeping the object with the value corresponding to 32.
Still learning Javascript and it's in a React Native Project. They changed some info of the API and it was working when I only had : culture":"32"
Code :
let newOrigin = [...new Set(this.state.secondArray.map(product => product.culture))],
visibleOrigin = firstArray.filter(item => newOrigin.includes(item.value));
this.setState({ displayOrigin: visibleOrigin });
How to get the value inside the array culture.
Any advice, any help ? Thank you.
So. I found a solution to my issue. Here's the code :
_nameFunction = () => {
let filteredSecondArray = [...new Set(this.state.secondArray.map(o => o.culture[0].value))];
this.setState({
firstArray: this.state.firstArray.filter(item => filteredSecondArray.includes(item.value))
})
}