How to update the data inside the nested Array - javascript

I have data structure like this:
const data = [
{
question: 'string of question',
parentId: '1',
userChoice: 'Yes',
child: []
},
{
question: 'string of question',
parentId: '2',
userChoice: 'No',
child: [
{
id: 6,
sub_question: 'sub question',
weightage: 1
},
{
id: 7,
sub_question: 'sub question',
weightage: 1
},
{
id: 8,
sub_question: 'sub question',
weightage: 1
}
]
}
]
I'm able to handle the first layer of data without child array.
This is how I'm doing;
// states
const deviceReport = localStorage.getItem('deviceReport') ? JSON.parse(localStorage.getItem('deviceReport')) : []
const [data, setData] = useState(deviceReport)
const [childData, setChildData] = useState([])
// To Put user response via question param to data and then persist it with localStorage
const handleClickYes = (question) => {
// find if question is in the data array
const hasQuestion = data.find(({ parentId }) => question.id === parentId)
const indexOfQuestion = data.indexOf(hasQuestion)
// copy data to mutable object
let newData = [...data]
if (hasQuestion) {
// update the value with specific selected index in the array.
newData[indexOfQuestion] = {
question: question.if_yes,
parentId: question.id,
userChoice: 'YES',
child: []
}
} else {
// concat existing data with a new question
newData = [
...newData,
{
question: question.if_yes,
parentId: question.id,
userChoice: 'YES',
child: []
}
]
}
localStorage.setItem('deviceReport', JSON.stringify(newData))
setData(newData)
}
Similary Now I want to add the data into child array with similar logic like If child array has same key then it should not concate otherwise it should concate the child array
I tried following this method to build childData first then concate into data.child[]
But this is not working even to collect the user response
function subQuestionsHandler(sub) {
// const hasId = data.child.some(({ id }) => sub.id === id)
const id = sub.id
const sub_question = sub.sub_question
const weightage = sub.weightage
// console.log(id, sub_question, weightage)
const hasId = childData.find(({ id }) => sub.id === id)
if (!hasId) {
setChildData((childData) => childData.concat({ id: id, sub_question: sub_question, weightage: weightage }))
}
}
So how can I concate the child array inside the data array. ?

Array:
let data = [
{
id: 1,
question: "...",
children: [],
},
{
id: 2,
question: "...",
children: [
{
id: 1,
sub_question: "...",
},
{
id: 2,
sub_question: "...",
},
],
},
];
Concat array:
data = [...data];
data.push({
id: 3,
question: "concatenated",
children: [],
});
Update array:
const datumIndexToUpdate = 0;
data = [...data];
data[datumIndexToUpdate] = { ...data[datumIndexToUpdate] };
data[datumIndexToUpdate].question = "updated";
Concat sub-array:
const datumIndexToUpdate = 0;
data = [...data];
data[datumIndexToUpdate] = { ...data[datumIndexToUpdate] };
data[datumIndexToUpdate].children = [...data[datumIndexToUpdate].children];
data[datumIndexToUpdate].children.push({
id: 3,
sub_question: "concatenated",
});
Update sub-array:
const datumIndexToUpdate = 0;
const childIndexToUpdate = 0;
data = [...data];
data[datumIndexToUpdate] = { ...data[datumIndexToUpdate] };
data[datumIndexToUpdate].children = [...data[datumIndexToUpdate].children];
data[datumIndexToUpdate].children[childIndexToUpdate] = {
...data[datumIndexToUpdate].children[childIndexToUpdate],
};
data[datumIndexToUpdate].children[childIndexToUpdate].sub_question = "updated";

This is how you concat an array
[1].concat([3]) === [1, 3]
This is how you concat a nested array
const nest1 = [[1]]
const nest3 = [[3]]
nest3[0].concat(nest1[0]) // === [[3, 1]]
Now I suggest simplifying your scenario and applying this logic to it. Or change the title in your question

Related

Compare one array with a nested array and push value into a new array with same index in Javascript

I have 2 arrays
const arrayOne = [
{id: '110'},
{id: '202'},
{id: '259'}
];
const arrayTwo = [
{data: [{value: 'Alpha', id: '001'}]},
{data: [{value: 'Bravo', id: '202'}]},
{data: [{value: 'Charlie', id: '110'}]},
{data: [{value: 'Delta', id: '202'}]}
];
I need to create a new array comparing arrayOne[idx].id with arrayTwo[idx].data[idx2].id
Upon match, I need to create an array pushing value (arrayTwo[idx].data[idx2].value) to the new array against each index in arrayOne.
In this example, I would get newArray = [null, 'Bravo', null, Delta]
What I have tried:
arrayOne.map(item => ({
...item,
result: arrayTwo.filter(itemTwo => item.data.map(x => x.id).includes(itemTwo.id))
}));
and also
const newArr = [];
arrayOne.map((item, idx) => {
if (arrayTwo.filter(itemTwo => itemTwo.data?.map(x => x.id) === item.id)) {
newArr.push(arrayTwo.data[idx].value);
} else newArr.push(null);
});
To do this you can map arrayTwo and use .find() to search for the ID in arrayOne. I also mapped arrayTwo to the inner object to make the second map more concise.
const arrayOne = [
{id: '110'},
{id: '202'},
{id: '259'}
];
const arrayTwo = [
{data: [{value: 'Alpha',id: '001'}]},
{data: [{value: 'Bravo',id: '202'}]},
{data: [{value: 'Charlie',id: '777'}]},
{data: [{value: 'Delta',id: '202'}]}
];
const result = arrayTwo
.map(obj => obj.data[0])
.map(obj => (arrayOne.find(v => v.id === obj.id) && obj.value) || null)
console.log(result)
Use map to iterate over each element of arr1 and return a new array.
Reassemble the data attribute array of each element in the arr2 array
using map and flat
When arr1 traverses, you can get the current element id, use filter
to filter the combined data array, and return an element array that matches
the current element id.
Based on the case where the id is not matched, use the optional chain operator to get the value.
When returning
if you want to get the element array of the id and
value attributes, use conditional (ternary) operator, when it doesn't match, return the original element,
when it matches, use spread syntax, copy the current element
attribute, and add the value attribute
if you only want to get an
array of matching results, just return the value,
remember to use the optional chain operator to convert the unmatched
value to null.
const arr1 = [
{ id: '110' },
{ id: '202' },
{ id: '259' }
];
const arr2 = [
{ data: [{ value: 'Alpha', id: '001' }] },
{ data: [{ value: 'Bravo', id: '202' }] }
];
const result1 = arr1.map(o1 => {
const data = arr2.map(o2 => o2.data).flat();
const value = data.filter(o2 => o2.id === o1.id)[0]?.value;
return value ? {...o1, value} : o1;
});
const result2 = arr1.map(o1 => {
const data = arr2.map(o2 => o2.data).flat();
const value = data.filter(o2 => o2.id === o1.id)[0]?.value;
return value ?? null;
});
[result1, result2].forEach(r => console.log(JSON.stringify(r)));
You can try this easy line of code :
const arrayOne = [{ id: '110' }, { id: '202' }, { id: '259' }];
const arrayTwo = [{ data: [{ value: 'Alpha', id: '001' }], }, { data: [{ value: 'Bravo', id: '202' }] }];
let result = arrayOne.map(el => {
let found = arrayTwo.find(f => f.data.at(0)?.id == el.id)?.data.at(0)?.value;
return { id: el.id, value: found ?? null};
});
console.log(result);

can access a specific position of an array but can't access specific object inside of this array

I'm having an issue that as the title say for itself..
I can access a specific position of an array but can't access specific object inside of this array..
export const getAccessLevel = id =>{
const accessLevels = [
{id: 0, name: 'Admin'},
{id: 1, name: 'Customer'},
{id: 2, name: 'Client'},
{id: 3, name: 'Viewer'}
]
let index = accessLevels.findIndex(array => array.id === id);
console.log(index) // result : 3
console.log(accessLevels[index])
// this is returning {id: 3, name: "Viewer"}
let AccessLevelName = accessLevels[index].name
// this is return error : Cannot read property 'name' of undefined
return AccessLevelName
}
In this example seems to be working, but I suggest you to use .find(), so you can have the object in just one call instead of getting it from an index. Just as
const getAccessLevel = id => {
const accessLevels = [
{ id: 0, name: "Admin" },
{ id: 1, name: "Customer" },
{ id: 2, name: "Client" },
{ id: 3, name: "Viewer" }
];
let accessLevel = accessLevels.find(array => array.id === id);
console.log(accessLevel);
let AccessLevelName = accessLevel.name;
return AccessLevelName;
};
I found the problem! It was happening because of react render order...
since the first time this function was called id was not defined..
which got the error right here :
let AccessLevelName = accessLevels[index].name
What I did to fix it was use useEffect hook.. when a action (redux) function change data value that has id's value, it execute getAccessLevel one more time, but with id defined!
getAccessLevel :
export const getAccessLevel = id => {
console.log(id)
const accessLevels = [
{ id: 0, name: "Admin" },
{ id: 1, name: "Customer" },
{ id: 2, name: "Client" },
{ id: 3, name: "Viewer" }
];
let accessLevel = accessLevels.find(array => array.id === id);
console.log(accessLevel);
let AccessLevelName = ''
if (id) {
AccessLevelName = accessLevel.name
}
return AccessLevelName;
}
UseEffect :
useEffect(() => {
props.findGroup(id)
}, [])
const handleAccessLevel = () =>{
setAccessLevelName(getAccessLevel(props.data.accessLevel))
}
useEffect(() => {
handleAccessLevel()
}, [props.data])

How to loop through array and push it into a seperate array based on some conditions using react?

i have an array named "all_items" and i want to split that to two arrays named "self" and "others".
I want to loop through each item in "all_items" array and if that item is added by current_user then push it to "self" array if not "others" array
item = {
id: 0,
owner_id: 1,
name: "name",
}
Below is the algorithm,
group = () => {
const self = [];
const others = [];
for each item
if item is uploaded by current_user
self.push(item)
else
others.push(item)
return {self, others}
}
How can i implement the above in react or javascript. Could someone help me with this. thanks.
A much better aproach is to use filter as follows
const group = () => {
const items = [
{
id: 0,
owner_id: 1,
name: "name",
},
{
id: 2,
owner_id: 2,
name: "user 2",
}
];
let current_user = { id: 1, name: "current user"}
const result = items.filter(item => item.owner_id != current_user.id);
const result2 = items.filter(item => item.owner_id == current_user.id);
return { result, result2}
}
There are a couple ways to approach this problem. I think the cleanest approach in terms of readability and length is to leverage Javascript's built in prototype Array.filter() method.
const self = all_items.filter(item => item.owner === current_user);
const others = all_items.filter(item => item.owner !== current_user);
Or for a solution that mirrors your example algorithm more closely, you can iterate over all_items with forEach() and push values to self and others as you go.
const self = [];
const others = [];
all_items.forEach(item => {
if (item.owner === current_user) {
self.push(item);
} else {
others.push(item);
}
});
**Also note that === will compare both type and value.
You can use filter
var all_items = [{
id: 0,
owner_id: 1,
name: "Matt",
},
{
id: 1,
owner_id: 2,
name: "Dave",
},
{
id: 2,
owner_id: 1,
name: "Mike",
},
{
id: 3,
owner_id: 3,
name: "Jack",
},
{
id: 4,
owner_id: 1,
name: "Paul",
}];
var current_user = {
id: 1
};
// self
console.log(all_items.filter(function(item){
return item.owner_id === current_user.id;
}));
// others
console.log(all_items.filter(function(item){
return item.owner_id !== current_user.id;
}));

Get list of duplicate objects in an array of objects

I am trying to get duplicate objects within an array of objects. Let's say the object is like below.
values = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' },
{ id: 11, name: 'someName3' },
{ id: 12, name: 'someName4' }
];
Duplicate objects should return like below:
duplicate = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' }
];
You can use Array#reduce to make a counter lookup table based on the id key, then use Array#filter to remove any items that appeared only once in the lookup table. Time complexity is O(n).
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const lookup = values.reduce((a, e) => {
a[e.id] = ++a[e.id] || 0;
return a;
}, {});
console.log(values.filter(e => lookup[e.id]));
Let's say you have:
arr = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
So, to get unique items:
unique = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) === i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]);
Then, result will be
unique = [
{ id:10, name: 'someName1' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
And, to get duplicate ids:
duplicateIds = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) !== i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]["id"])
List of IDs will be
duplicateIds = [10]
Thus, to get duplicates objects:
duplicate = arr.filter(obj=> dublicateIds.includes(obj.id));
Now you have it:
duplicate = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' }
]
Thanks https://reactgo.com/removeduplicateobjects/
You haven't clarified whether two objects with different ids, but the same "name" count as a duplicate. I will assume those do not count as a duplicate; in other words, only objects with the same id will count as duplicate.
let ids = {};
let dups = [];
values.forEach((val)=> {
if (ids[val.id]) {
// we have already found this same id
dups.push(val)
} else {
ids[val.id] = true;
}
})
return dups;
With lodash you can solve this with filter and countBy for complexity of O(n):
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const counts = _.countBy(data, 'id')
console.log(_.filter(data, x => counts[x.id] > 1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could do the same with ES6 like so:
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const countBy = (d, id) => d.reduce((r,{id},i,a) => (r[id] = a.filter(x => x.id == id).length, r),{})
const counts = countBy(data, 'id')
console.log(data.filter(x => [x.id] > 1))
You can use an array to store unique elements and use filter on values to only return duplicates.
const unique = []
const duplicates = values.filter(o => {
if(unique.find(i => i.id === o.id && i.name === o.name)) {
return true
}
unique.push(o)
return false;
})
With lodash you can use _.groupBy() to group elements by their id. Than _.filter() out groups that have less than two members, and _.flatten() the results:
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const result = _.flow([
arr => _.groupBy(arr, 'id'), // group elements by id
g => _.filter(g, o => o.length > 1), // remove groups that have less than two members
_.flatten // flatten the results to a single array
])(values);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
An alternative based in #ggorlen solution with new Map() as accumulator (for better performance) and without unary operator ++ (not advised by default in projects with ESLint).
const values = [{ id: 10, name: "someName1" }, { id: 10, name: "someName2" }, { id: 11, name: "someName3" }, { id: 12, name: "someName4" },];
const lookup = values.reduce((a, e) => {
a.set(e.id, (a.get(e.id) ?? 0) + 1);
return a;
}, new Map());
console.log(values.filter(e => lookup.get(e.id) > 1));
Try this
function checkDuplicateInObject(propertyName, inputArray) {
var seenDuplicate = false,
testObject = {};
inputArray.map(function(item) {
var itemPropertyName = item[propertyName];
if (itemPropertyName in testObject) {
testObject[itemPropertyName].duplicate = true;
item.duplicate = true;
seenDuplicate = true;
}
else {
testObject[itemPropertyName] = item;
delete item.duplicate;
}
});
return seenDuplicate;
}
referred from : http://www.competa.com/blog/lets-find-duplicate-property-values-in-an-array-of-objects-in-javascript/

How to change an array of objects containing ids and children references into a nested tree structured object?

I would like to create an object with a tree structure from data that looks as follow:
nodes: [
{ name: "Ben", id: 1 next: [2, 3], depth: 0 },
{ name: "Mike", id: 2, next: [4, 5], depth: 1 },
{ name: "Jen", id: 3, next: [6], depth: 1 },
{ name: "Sue", id: 4, next [], depth: 2 },
{ name: "Jeff", id: 5, next: [], depth: 2 },
{ name: "Bob", id: 6, next: [], depth: 3 }
]
The tree like object would look like this:
root:
{ name: "Ben", children:
[
{ name: "Mike", children:
[
{ name: "Sue" },
{ name: "Jeff" }
]
},
{ name: "Jen", children:
[
{ name: "Bob" }
]
}
]
}
I can assign the root and go through the objects in the next array like this:
const root = { name: nodes[0].name };
root.children = [];
nodes[0].next.map(function (next) {
nodes.map((node, i) => {
if (next === node.id) {
root.children.push({name: nodes[i].name})
}
})
});
I'm not sure how to find next for the nodes pushed to the children array. The number of objects in the nodes array may vary, so the depth of children arrays may vary too. How can you create a new children array based on this variable and push the right properties to it?
First pass, map the nodes by id into something resembling the desired output. This has the added benefit of not mutating the original nodes objects
const idMap = nodes.reduce((map, { id, name }) => map.set(id, { name }), new Map())
Then iterate the nodes, reducing that to an array of roots
const roots = nodes.reduce((arr, node) => {
let obj = idMap.get(node.id)
if (node.next && node.next.length) {
obj.children = node.next.map(id => idMap.get(id))
}
if (node.depth === 0) {
arr.push(obj)
}
return arr
}, [])
Then find the first one with children
const root = roots.find(rootNode => rootNode.children)
const nodes = [{"name":"Ben","id":1,"next":[2,3],"depth":0},{"name":"Mike","id":2,"next":[4,5],"depth":1},{"name":"Jen","id":3,"next":[6],"depth":1},{"name":"Sue","id":4,"next":[],"depth":2},{"name":"Jeff","id":5,"next":[],"depth":2},{"name":"Bob","id":6,"next":[],"depth":3}]
const idMap = nodes.reduce((map, { id, name }) => map.set(id, { name }), new Map())
const roots = nodes.reduce((arr, node) => {
let obj = idMap.get(node.id)
if (node.next && node.next.length) {
obj.children = node.next.map(id => idMap.get(id))
}
if (node.depth === 0) {
arr.push(obj)
}
return arr
}, [])
const root = roots.find(rootNode => rootNode.children)
console.info(root)
You can use recursive function and go until next array length is 0 !!
var nodes = [
{ name: "Ben", id: 1, next: [2,3], depth: 0},
{ name: "Mike", id: 2, next: [4,5], depth: 1},
{ name: "Jen", id: 3, next: [6], depth: 1},
{ name: "Sue", id: 4, next: [], depth: 2},
{ name: "Jeff", id: 5, next: [], depth: 2 },
{ name: "Bob", id: 6, next: [], depth: 3 }
];
var root = {};
nodes.forEach(v => {
if(v.depth == 0) { // check root 0
root.name = v.name
if(v.next.length > 0 ) { // check next array has more than 0
root.children = []
findChild(root,v);
}
}
});
function findChild(root,c) {
c.next.forEach(v => {
var child = nodes.find(x => x.id === v)
var next = {name: child.name};
if(child.next.length > 0) {
next.children = [];
findChild(next,child);
}
root.children.push(next); // push to real root
})
}
console.log(root);
You should probably use an id lookup table:
const hash = {};
for(const node of nodes)
hash[node.id] = node;
for(const node of nodes) node.children = node.next.map(id => ((hash[id].parent = node), hash[id]));
const roots = nodes.filter(n => !n.parent);
As there could be multiple roots, this gives an array of roots.

Categories

Resources