Merge an item attribute of one array into items of another array - javascript

I have a few questions in regards to what would be the best approach to do the following:
Call two different API:
axios.get(contents);
axios.get(favorites);
Response will Look like this:
contents: [
{
id: 1,
value: someValue
},
{
id: 2,
value: someValue
}
];
favorites: [
{
id: 1,
contentId: 2
}
];
What would be the best approach to loop through each favorite and add an element to the contens array such as isFavorite: true when the contentId matches the id. It should look as follows:
contents: [
{
id: 1,
value: someValue
{,
{
id: 2,
value: someValue
isFavorite: true
{
];
What would be the best place to do this and is there any ES6 syntax that can easily do this? I currently have the two actions separate, one that gets the contents and one that gets the favorites, I could possibly merge those or combine them at the reducer.
Any suggestions?

You can use a Set to collect all contentId values from favorites and then iterate through your contents array. This has better time complexity than using some on an array because calling .has() on a Set is O(1):
let contents = [{
id: 1,
value: 'someValue1'
},
{
id: 2,
value: 'someValue2'
},
{
id: 3,
value: 'someValue'
}
];
let favorites = [{
id: 1,
contentId: 2
},
{
id: 2,
contentId: 3
}
];
let favoriteContents = new Set(favorites.map(f => f.contentId));
contents.forEach(c => {
if (favoriteContents.has(c.id)) c.isFavorite = true;
});
console.log(contents);

const newContents = contents.map((content) => {
const foundFavorite = favorites.find((favorite) => favorite.contentId === content.id)
if (foundFavorite) {
return {
...content,
isFavorite: true,
}
}
return content
});

You firstly need to have the promises from your API calls, and when both of them are complete you can then carry out the merge of the results.
const contentsApi = () => Promise.resolve([
{
id: 1,
value: 'foo'
},
{
id: 2,
value: 'bar'
}
])
const favouritesApi = () => Promise.resolve([
{
id: 1,
contentId: 2
}
])
let contents;
let favourites;
const contentsApiCall = contentsApi().then(res => {
contents = res;
})
const favouritesApiCall = favouritesApi().then(res => {
favourites = res;
})
Promise.all([contentsApiCall, favouritesApiCall]).then(() => {
const merged = contents.map(content => {
if(favourites.some(favourite => favourite.contentId === content.id)){
return {
...content,
isFavourite: true
}
} else {
return content;
}
})
console.log(merged)
// do whatever you need to do with your result, either return it if you want to chain promises, or set it in a variable, etc.
})

Related

Retain array structure when filtering nested array

My brain froze with this advanced filtering. This task has exceeded my basic knowledge of filter, map etc.
Here I have an array with nested objects with array:
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
You may have seen this sort of style if you've worked with React Native (RN). This question is not for RN. I need to perform a filter on the name property in the nested array and when I get a match, I must return the format as the DATA variable.
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
console.log(results);
};
My limited knowledge of deep filtering returns the basic filtering for the data array but need to retain the structure for DATA. The expected results I'd expect:
// I'm now querying for "ZAMASU"
const handleFiltering = (value='ZAMA') => {
const _value = value.toLowerCase();
const results = DATA.map(o => {
return o.data.filter(o => o.name.toLowerCase().indexOf(_value) != -1)
});
// console.log(results) should now be
// [
// {
// title: 'Dragon Balls Z',
// data: [
// { id: 2, name: 'Zamasu' }
// ]
// }
// ];
};
What comes to mind is the use of {...DATA, something-here } but my brain has frozen as I need to get back the title property. How to achieve this, please?
Another solution would be first use filter to find only objects containing the name in data passed through the argument, subsequently mapping data.
Here is your adjusted filter method
const handleFiltering = (value) => {
const _value = value.toLowerCase();
const results = DATA.filter((obj) =>
obj.data.some((character) => character.name.toLowerCase() === _value)
).map((obj) => ({
title: obj.title,
data: obj.data.filter(
(character) => character.name.toLowerCase() === _value
),
}));
console.log(results);
};
You can use reduce method of array. First find out the object inside data array and then add that to accumulator array as new entry by preserving the original structure.
const DATA = [
{
title: 'Spongebob',
data: [
{ id: 1, name: 'Mr Crabs', where: 'tv' },
{ id: 2, name: 'Sandy' }
]
},
{
title: 'Dragon Balls Z',
data: [
{ id: 1, name: 'GoKu' },
{ id: 2, name: 'Zamasu' }
]
}
];
let handleFiltering = (value='tv') => {
return DATA.reduce((acc,d) => {
let obj = d.data.find(a => a.name?.toLowerCase().includes(value.toLowerCase())
|| a.where?.toLowerCase().includes(value.toLowerCase()));
obj ? acc.push({...d, data:[obj]}) : null;
return acc;
}, []);
}
let result = handleFiltering();
console.log(result);

How to remove data from object tree

Hi I have object like this:
Subject {
id:1
packages: [
{id:1, isChecked:true, ...},
{id:2, isChecked:false, ...},
{id:3, isChecked:true, themes:[
{id:1, isChecked:true},
{id:1, isChecked:false},
]
]
...
}
How can i remove all not checked items from this object please? I have to send this updated object to another component in react app.
The real tree looks like this:
Subject
|-packages
|-themes (check if checked)
|-themeParts (check if checked)
|-exercises (check if checked)
If any of child is checked it should be added to new component. So if I have Two packages and only one exercise is checked it is also checked themmeparts of this exercise, also theme of theme part and theme. Packages don't have isChecked attribute but i have to add this level to new object too if any of its child is checked.
Other example... if second package has no theme,part or exercise checked i have to remove from package level down alll...
So when i finish i need to have only Subject{} object with checked items + package of that checked items...
I hope i described it good XD....
anyway i tried something like this:
returnSelectedItems(){
console.log(this.state.data);
let newData = cloneDeep(this.state.data);
newData.packages = [];
this.state.data.packages.forEach((pckg) => {
const newPackage = {
};
pckg.themes.forEach((theme, key) => {
if(theme.isChecked){
}
});
});
console.log(newData);
console.log(newData.packages);
console.log(newData.packages[0].themes);
console.log(newData.packages[0].themes[0].themeParts);
}
But this is useless i think and i really don't know how to od it properly and ezy as it can be.. Thx for help
You can create a generic function like this. This takes an array as input and reduces it recursively for any nested array property. Destructure the object and get the array property to a rest object. If the rest object has any keys, recursively call the function to filter for the isChecked property.
This will work for any name of the array property for any number of nesting
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const output = {
id: input.id,
packages: getChecked(input.packages)
};
Here's a snippet:
function getChecked(array) {
return array.reduce((acc, { id, isChecked, ...rest }) => {
if (isChecked) {
const o = { id, isChecked };
const [key] = Object.keys(rest);
if (key)
o[key] = getChecked(rest[key]);
acc.push(o)
}
return acc;
}, [])
}
const input = {
id: 1,
packages: [
{ id: 1, isChecked: true },
{ id: 2, isChecked: false },
{ id: 3, isChecked: true, themes: [
{
id: 4, isChecked: true, themeParts: [
{ id: 5, isChecked: true, exercises: [
{ id: 7, isChecked: true },
{ id: 8, isChecked: false }
]
},
{ id: 6, isChecked: true }
]
},
{ id: 9, isChecked: false }
]
},
{ id: 10, isChecked: true },
]
};
const output = {
id: input.id,
packages: getChecked(input.packages)
};
console.log(output)
I believe this is what you want. That's parent cannot be removed if children(themes) need to be kept
var subjects = {
id: 1,
packages: [{
id: 1,
isChecked: true,
},
{
id: 2,
isChecked: false,
},
{
id: 3,
isChecked: true,
themes: [{
id: 1,
isChecked: true
},
{
id: 1,
isChecked: false
},
]
}
]
};
function purge(item) {
purgeItems(item.themes ||[]);
return !item.isChecked && (item.themes ||[]).length === 0;
}
function purgeItems(themes) {
var toremove = [];
themes.forEach(function(p, i) {
if (purge(p)) {
toremove.push(i)
}
});
while (toremove.length > 0) {
themes.splice(toremove.pop(), 1);
}
}
purgeItems(subjects.packages);
console.log(JSON.stringify(subjects));
This is what i needed. using this principe: let {packages: packages, ...newData} = tempData;
returnSelectedItems(){
let tempData = cloneDeep(this.state.data);
let {packages: packages, ...newData} = tempData;
this.state.data.packages.forEach(pckg => {
const {themes,...newPckg} = pckg;
newPckg.themes = [];
pckg.themes.forEach(theme =>{
if(!theme.isChecked){
return;
}
const {themeParts, ...newTheme} = theme;
newTheme.themeParts =[];
newPckg.themes.push(newTheme);
theme.themeParts.forEach(part =>{
if(!part.isChecked){
return;
}
const {knowledges, ...newPart} = part;
newPart.knowledges = [];
newTheme.themeParts.push(newPart);
part.knowledges.forEach(knowledge =>{
if(!knowledge.isChecked){
return;
}
newPart.knowledges.push(knowledge);
});
});
});
if(newPckg.themes.length > 0){
newData.packages = newPckg;
}
});
return newData;
}

Update one property value of one object in array of objects, return updated array

I am trying to update one value is_deleted in an array of objects for the deleted object.
const handleDelete = (idx) => {
const filteredCards = cards.map((card, i) => {
if(i == idx) {
card.is_deleted = true;
return {...cards}
}
})
setCards(filteredCards);
}
Output
cards = [
undefined,
[
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: true },
]
]
Desired Output
cards = [
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: true },
]
The output is partially right - at index 1 I see the two cards, with the second is_deleted being set to true. However, at index 0 there is an "undefined".
Any insight into why this is would be helpful! Thank you and let me know if I missed a key piece of info.
You could just access the card directly, rather than using map:
let cards = [
{ id: 1, is_deleted: false },
{ id: 2, is_deleted: false },
]
const setCards = c => cards = c(cards.slice())
const handleDelete = idx => setCards(cards => (cards[idx].is_deleted = true, cards))
handleDelete(1)
console.log(cards)
The setCards function is just mimicking react's useState.

How to remove child by id and retrive the parent objects using filter

I am trying to filter the parent, by removing it's child id only by not matching. in case if there is no child exist, the parent should be removed.
I try like this, but not works.
var rm = 7;
var objects = [
{
name: "parent1",
id: 1,
blog: [
{
name: "child1",
id: 1
},
{
name: "child2",
id: 2
}
]
},
{
name: "parent2",
id: 2,
blog: [
{
name: "child3",
id: 3
},
{
name: "child4",
id: 4
}
]
},
{
name: "parent3",
id: 3,
blog: [
{
name: "child5",
id: 5
},
{
name: "child6",
id: 6
}
]
},
{
name: "parent4",
id: 3,
blog: [
{
name: "child6",
id: 7
}
]
},
]
var result = objects.filter(value => {
if(!value.blog) return;
return value.blog.some(blog => blog.id !== rm)
})
console.log(result);
What is wrong here, or some one show me the correct approach?
looking for :
need to remove the blog if the id is same as rm, parent with other children required to exist.
need to remove the parent, after remove the children, in case there is no child(blog) exist.
Live Demo
Loop through the list of parents, and inside that loop, try to remove blogs with the given id first. Once you have done that, you can check if the blogs property has became empty, and if so, filter it out:
// We're going to filter out objects with no blogs
var result = objects.filter(value => {
// First filter blogs that match the given id
value.blog = value.blog.filter(blog => blog.id !== rm);
// Then, if the new length is different than 0, keep the parent
return value.blog.length;
})
I think the below code is what you are looking for
var result = objects.map(value => {
const blog = value.blog.filter(blog => blog.id !== rm);
if(blog.length === 0) {
return;
}
value.blog = blog;
return value;
}).filter(item => item);
Demo: https://jsfiddle.net/7Lp82z4k/3/
var result = objects.map(parent => {
parent.blog = parent.blog.filter(child => child.id !== rm);
return parent}).filter(parent => parent.blog && parent.blog.length > 0);

Create object with new key and assign same array

Do you have an optimised solution for the following.
let say,
x = { users: [ {id: 1}, {id:2}, {id:3} ] }
I want to make a new key with same values, the output should be,
{ users: { list: [ {id: 1}, {id:2}, {id:3} ], count: 3 }
Using only JS or Underscore with one line code w/o any extra effort.
I know other tricks to do the same, but do we have one line solution for the same ?
Help appreciated...
Thanks.
Create the object, and assign the x array to list. However, count should be a getter, since the list length might change:
const x = { users: [{ id: 1 }, { id: 2 }, { id: 3 }] };
const result = {
users: {
list: x.users,
get count() { return this.list.length; }
}
};
console.log(JSON.stringify(result));
result.users.list.push({ id: 4 });
console.log(JSON.stringify(result));
I'm not sure why this is to be an optimised solution because this is a simple plain-JavaScript problem:
let x = { users: [{ id: 1 }, { id: 2 }, { id: 3 }] };
let result = {
users: {
list: x.users,
count: x.users.length
}
};
console.log(result);
Sure, just define the property as an object
const obj = {
users: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
};
obj.users = { list: obj.users, count: obj.users.length };
console.log(obj);
I recommend focusing on code clarity rather than on line conservation though

Categories