Remove duplication of a specific attribute of an object inside an array - Angular - arrays

Consider the following array:
packages = [
{modelName: "flatRate", name:"Enterprise", price: "$150"},
{modelName: "flatRate", name:"Gold", price: "$190"},
{modelName: "usageBased", name:"Enterprise", price: "$50"},
{modelName: "userBased", name:"Extreme", price: "$50"},
]
What I want to achieve is:
Use the packages.modelName as a heading and list all its items under it without repeating modelName. For Example:
flatRate
Enterprise ($150) , Gold ($190)
usageBased
Enterprise ($50)
userBased
Extreme ($50)
Notice how 2 objects having modelName: "flatRate" are shown under one heading.
What I've tried:
Take the packages array use a foreach loop on it and remake the array as:
packagesResult = [
{ modelname: "flatRate",
model: [ {name: "Enterprise", price: "$150"} ]
},
{ modelname: "flatRate",
model: [ {name: "Gold", price: "$190"} ]
},
{ modelname: "usageBased",
model: [ {name: "Enterprise", price: "$50"} ]
},
{ modelname: "userBased",
model: [ {name: "Extreme", price: "$50"} ]
},
]
and then tried to use a filter on it using observable but didn't work. Any help will be highly appreciated. Thanks.

Would a reduce like so fit your purpose?
models = packages.reduce((models, package) => {
models[package.name] = package.price;
return models;
}, {})

I would opt for something like the following:
myPackages = {};
function consolidatePayload() {
for(let package of packages){
if(this.myPackages.hasOwnProperty(package.modelName)) {
this.myPackages[package.modelName][package.name] = package.price;
} else {
this.myPackages[package.modelName]= {[package.name]: package.price};
}
}
console.log(this.myPackages);
}
IT gives an and result along the lines of
{
flatRate: {
Enterprise: "$150",
Gold: "$190"
},
usageBased: {
Enterprise: "$50"
},
userBased: {
Extreme: "$50"
}
}
EDIT: reduce inspiration, per #Śaeun acreáť
function consolidatePayload() {
let models = this.packages.reduce((models, package) => {
if(models.hasOwnProperty(package.modelName)) {
models[package.modelName][package.name]= package.price;
} else {
models[package.modelName] = {[package.name]: package.price}
}
return models;
}, {})
}

You can create array of price types for each model name like below ;)
const result = packages = [{
modelName: "flatRate",
name: "Enterprise",
price: "$150"
},
{
modelName: "flatRate",
name: "Gold",
price: "$190"
},
{
modelName: "usageBased",
name: "Enterprise",
price: "$50"
},
{
modelName: "userBased",
name: "Extreme",
price: "$50"
},
].reduce( (acc, item, i) => {
if( i === 0 ) {
acc.push(modifiedObject(item));
return acc;
}
let foundItem = acc.find(it => it.modelName === item.modelName);
if (foundItem) {
addNewPriceType(foundItem, item);
return acc;
} else {
acc.push(modifiedObject(item));
return acc;
}
}, [] );
function modifiedObject(item) {
return {
modelName : item.modelName,
model : [
{ name : item.name, price : item.price }
]
};
}
function addNewPriceType(foundItem, item) {
foundItem.model.push({
name : item.name,
price : item.price
});
}
console.log(result);

Related

How to update nested array of objects in ReactJS

I have an array of object like this:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
],
},];
Now I want to add more animations inside this array, such that each object gets added in the fanimate such that:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
{
id: "222",
animate: "def",
},
],
},];
I tried using the hook useState, but I am getting undefined results
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz",
},
],
}];
const modified = tempobj.map(temp => {
const newtemp = {
id: temp.id,
fanimate: [...temp.fanimate, {id:"222", animate:"def"}]
}
return newtemp;
})
console.log(modified);
You could just spread all the places
const tempObj = {
id:'1',
fan:[
{
id:'2',
animate:'xyz'
}
]
}
console.log(tempObj)
const newtest={...testObj,fan:[...testObj.fan, {id:'3', animate:'tuz'}]}
console.log(newtest)
Try this:
const tempobj = [
{
id: "1",
fanimate: [
{
id: "111",
animate: "xyz"
}
]
}
];
const [state, setState] = useState(tempobj);
function updateArray(newItem) {
setState(
state.map((item) => ({ ...item, fanimate: [...item.fanimate, newItem] }))
);
}
You can push the newItem to the original fanimate array using spread operator, everytime a new item is added, the original array data is copied by ...item.fanimate:
{ ...item, fanimate: [...item.fanimate, newItem] }
BTW the naming of tempobj really should be tempArr or tempArray.
A working sandbox

React: How to render a list of items grouped by category (using .map)

I have an array (myArray), stored like so (in firebase):
[
{
"id": "1",
"Category": "Hardware",
"Name": "Xtreme"
},
{
"id": "123",
"Category": "Software",
"Name": "Obsolete"
},
{
"id": "12345",
"Category": "Software",
"Name": "V1"
},
{
"id": "1234567",
"Category": "Hardware",
"Name": "CPU"
}
]
I am using the following code:
const sorterAR = [];
myArray.forEach((item) => {
let cat = sorterAR.find(
(cat) => cat.id === item.id
);
if (!cat) {
cat = {
id: item.id,
Category: item.Category,
items: [],
};
sorterAR.push(cat);
}
cat.items.push(item);
});
And then displaying like so:
<div className="App">
{sorterAR.map((cat) => (
<>
<div>
<b>{cat.Category}</b>
</div>
<ul>
{cat.items.map((item) => (
<li>{item.Name}</li>
))}
</ul>
</>
))}
</div>
This works in that it produces an output like:
**Hardware**
Xtreme
**Hardware**
CPU
**Software**
Obsolete
**Software**
V1
How do I alter this to produce the following output:
**Hardware**
Xtreme
CPU
**Software**
Obsolete
V1
So that it displays the category name and then all the items in that category, and then moves to the next one and so forth?
I assumed that order doesn't matter if Hardware or Software should come first.
First I categorized the array into an object of Category objects using Array.prototype.reduce().
From the resultant object you can build the JSX
var data1 = [
{
id: '1',
Category: 'Hardware',
Name: 'Xtreme',
},
{
id: '123',
Category: 'Software',
Name: 'Obsolete',
},
{
id: '12345',
Category: 'Software',
Name: 'V1',
},
{
id: '1234567',
Category: 'Hardware',
Name: 'CPU',
},
];
const categorizedData = data1.reduce((acc, curr) => {
const { id, Category, Name } = curr;
if (!acc[Category]) {
acc[Category] = {
items: [],
};
}
acc[Category].items.push(Name);
return acc;
}, {});
console.log(categorizedData);
Object.keys(categorizedData).map((key, index) => {
console.log(`Category: ${key}`);
categorizedData[key].items.map((item, index) =>
console.log(`Item ${index}: ${item}`)
);
});

Convert array of Objects into a grouped array of Objects Typescript

I'm trying to convert an array of objects like this:
[{grandParentField:'grandParent1', parentField:'parent1', childField: 'child1'},
{grandParentField:'grandParent1', parentField:'parent1', childField: 'child2'},
{grandParentField:'grandParent2', parentField:'parent1', childField: 'child3'},
{grandParentField:'grandParent2', parentField:'parent2', childField: 'child4'}]
into this form:
[
{
text: 'grandparent1',
items: [
{
text: 'parent1',
items: [{ text: 'child1' }, { text: 'child2' }]
}
]
},
{
text: 'grandparent2',
items: [
{
text: 'parent1',
items: [{ text: 'child3' }]
},
{
text: 'parent2',
items: [{ text: 'child4' }]
}
]
}
]
This Thread is similar to what I want, but not quite.
children will always be unique, but parents can have multiple grandparents.
Honestly I've tried so many things I'm not even sure which one to include as an example of what has gotten me closest.
Something like this but able to take in an array of Objects, and pump out the {text: string, items:[{text: string, items:[{text:string]]} structure:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Recursive approach, should work for every n-nested input that you will provide:
const input =[{grandParentField:"grandParent1",parentField:"parent1",childField:"child1"},{grandParentField:"grandParent1",parentField:"parent1",childField:"child2"},{grandParentField:"grandParent2",parentField:"parent1",childField:"child3"},{grandParentField:"grandParent2",parentField:"parent2",childField:"child4"}];
const nestedGroupBy = (nodes, order, orderIdx = 0) => {
const key = order[orderIdx]
let grouped = nodes.reduce((acc, e, i) => {
let node = acc.find(x => x.text == e[key])
if (!node) {
node = { text: e[key], items: [] }
acc.push(node)
}
node.items ? node.items.push(e) : node.items = [e]
return acc
}, [])
if (order[orderIdx + 1])
grouped = grouped.map(e => ({
text: e.text,
items: nestedGroupBy(e.items, order, orderIdx + 1)
}))
else
grouped = grouped.map(e => ({ text: e.text }) )
return grouped
}
const res = nestedGroupBy(input, Object.keys(input[0]))
console.log(res)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Without getting too crazy with types, I'd say that you want your output to be of this shape:
interface Tree {
text: string,
items?: Tree[]
}
So let's make a function called group() which takes your array and a list of keys that you want to process in the order they should be processed. So for your example it would be used like this:
const data = [
{ grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child1' },
{ grandParentField: 'grandParent1', parentField: 'parent1', childField: 'child2' },
{ grandParentField: 'grandParent2', parentField: 'parent1', childField: 'child3' },
{ grandParentField: 'grandParent2', parentField: 'parent2', childField: 'child4' }
];
const groupedData = group(data, "grandParentField", "parentField", "childField");
Here's the implementation of group():
function group(data: Array<Record<string, string>>, key: string, ...otherKeys: string[]): Tree[] {
const objMap: Record<string, any[]> = {}
for (const d of data) {
if (!(d[key] in objMap)) {
objMap[d[key]] = []
}
objMap[d[key]].push(d);
}
return Object.keys(objMap).map(k => otherKeys.length ?
{
text: k,
items: group(objMap[k], otherKeys[0], ...otherKeys.slice(1))
} : {
text: k
}
);
}
First we group the elements from data into a dictionary of arrays called objMap, where each element d goes into the key of objMap at d[key] (so the first element goes into the key named "grandParent1" if key is "grandParentField").
Once this grouping is done, we return a new array by walking through objMap's keys. If we have no otherKeys, we just return an array of {text: string} elements using the keys of objMap as the text field. If we do have other keys, then we need to recursively call group() on the elements stored in objMap at the proper key.
You can verify that this works for your example:
console.log(JSON.stringify(groupedData, undefined, 2));
/* [
{
"text": "grandParent1",
"items": [
{
"text": "parent1",
"items": [
{
"text": "child1"
},
{
"text": "child2"
}
]
}
]
},
{
"text": "grandParent2",
"items": [
{
"text": "parent1",
"items": [
{
"text": "child3"
}
]
},
{
"text": "parent2",
"items": [
{
"text": "child4"
}
]
}
]
}
] */
Playground link to code

React - setState with certain index

I've been stuck for whole day and please help me to fix it.
I have a json data which like this :
[
{
"menu": "menu_1",
"icon": "icon_1",
"detail": {
"name": "name_1",
"phone": "phone_1"
}
},
{
"menu": "menu_2",
"icon": "icon_2",
"detail": {
"name": "name_2",
"phone": "phone_2"
}
},
{
"menu": "menu_3",
"icon": "icon_3",
"detail": {
"name": "name_3",
"phone": "phone_3"
}
}
]
I put them into the "data" state and My goal is I wanna change the "detail" state with certain index ( ex: state "data" with index 1 change the "detail" data )
Currently my code is :
this.setState({
data: {
...this.state.data,
detail:{
this.state.data[1].detail:{
"name": "billy",
"phone": "893823839"
}
}
}
})
That setState is clearly wanna change the state with certain index but fail..
How do I supposed to do?
I guess this is what you're looking for, we could replace an element inside an array using splice :
const index = 1;
this.setState({
data: [...this.state.data].splice(index, 1, {
...this.state.data[index],
details: { name: "billy", phone: "893823839" },
}),
});
Update: we could use slice also to make an immutable update with index :
this.setState({
data: [
...this.state.data.slice(0, index),
{
...this.state.data[index],
details: { name: "billy", phone: "893823839" },
},
...this.state.data.slice(index + 1, this.state.data.length),
],
});
could you try it ?
this is an example that i tested using splice:
const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
const indexToBeModified = 1; // { id: 2 } ==> { foo: "foo", id: 2 }
items.splice(indexToBeModified, 1, { ...items[indexToBeModified], foo: "foo" });
console.log("items", items);
Here is a little modified example. It uses prevState to prevent any unwanted changes that may happen when directly interacting with this.state.
import React, { Component } from "react";
export default class App extends Component {
constructor() {
super();
this.state = {
data: [
{
menu: "menu_1",
icon: "icon_1",
detail: {
name: "name_1",
phone: "phone_1"
}
},
{
menu: "menu_2",
icon: "icon_2",
detail: {
name: "name_2",
phone: "phone_2"
}
},
{
menu: "menu_3",
icon: "icon_3",
detail: {
name: "name_3",
phone: "phone_3"
}
}
]
};
this.modifyData = this.modifyData.bind(this);
}
modifyData(index) {
this.setState((prevState) => {
prevState.data[index].detail={
name: "billy",
phone: "893823839"
};
return {
data: [prevState.data]
};
},()=>{console.log(this.state.data)});
}
render() {
return (
<button onClick={() => this.modifyData(0)}>Click to modify data</button>
);
}
}
Here is a code sandbox reference.

How can I combine the results of 3 queries in MongoDB?

I made the following filter in hopes that I would be combining the results from all 3 $and arrays but it is only matching one of those blocks.
How can I combine the results of what would be returned from each $and array if conditions are met. Hopefully that's clear. I don't know what to call the $and array.
const filter = {
$or: [
{
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
name: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
description: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
$and: [
{ category: req.query.category },
{ tags: req.query.subCategory },
{contentType: req.query.contentType},
req.query.searchTerm !== ""
? {
tags: {
$regex: "(?i)" + req.query.searchTerm + "(?-i)",
$options: "i",
},
}
: {},
],
},
],
};
await Content.paginate(filter, options, (err, result) => {
if (err) {
res.status(500).send(err);
} else {
res.json(result);
}
});
EDIT: Below is an example of two entries that would be found in the database. The way it should work is it should use category, subCategory, and contentType to filter out the entries in the database so that what I have now are only the entries which have the same category, subCategory, and contentType as specified in req.query, I'll call this the firstFilterResult. From there, I am trying to search within firstFilterResult to see if I have entries that have name, tag, or description matches. So basically catgeory, subCategory and contentType are just used to narrow down the results so that I can find matches for name, tag, and description. My code above doesn't do exactly this but this is the idea behind it and I thought that what I have would do similar, but I guess I'm wrong.
contents: [
{
tags: [
'food',
'drinks',
'card',
'account'
],
_id: '1d13ff7m6db4d5417cd608f4',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'In here I will begin to talk about...',
content_id: '5dbcb998ad4144390c244093',
contentType: 'quiz',
date: '2019-06-03T04:00:00.000Z',
category: 'food',
image: 'https://IMAGE.PNG',
__v: 0
},
{
tags: [
'computer',
'laptop'
],
_id: '7d1b940b1c9d44000025db8c',
name: 'THE NAME FOR THIS PIECE OF CONTENT',
description: 'This is another description',
content_id: '5f1b963d1c9d44000055db8d',
contentType: 'tool',
date: '2019-06-03T04:00:00.000Z',
category: 'money',
image: 'https://IMAGE.PNG',
__v: 0
}
]
I finally got it to work with this
const catFilter =
req.query.category !== "" ? { category: req.query.category } : {};
const subCatFilter =
req.query.subCategory !== "" ? { tags: req.query.subCategory } : {};
const typeFilter =
req.query.contentType !== ""
? { contentType: req.query.contentType }
: {};
const filter = {
$and: [
{
$or: [
{
name: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
description: {
$regex: req.query.searchTerm,
$options: "i",
},
},
{
tags: {
$regex: req.query.searchTerm,
$options: "i",
},
},
],
},
catFilter,
subCatFilter,
typeFilter,
],
};
Since each element of the $or contains the same 3 checks with a single one that varies, these can be separated out, and the $or is then only needed if a search term is specified.
Passing options:"i" makes the entire regex match case insensitive, so it is not necessary to surround the search string with (?i) and (?-i)
The following should build the filter that you are attempting, without using empty objects:
// base query that checks the common fields
var filter = {
category: req.query.category,
tags: req.query.subCategory,
contentType: req.query.contentType
};
// if a search term is provided, add in the additional critera
if (req.query.searchTerm !== "") {
var regex = {
$regex: req.query.searchTerm,
options:"i"
};
filter['$or'] = [
{ name: regex },
{ description: regex },
{ tags: regex }
]
}
If this doesn't obtain the results you're after, please edit the question and add in some sample documents so we can see the problem.

Resources