Traversing and build Tree from fetched child details in NodeJS [duplicate] - javascript

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
I am trying to build a tree structure by traversing a given ID and attaching its child to the tree. It always assigns user.children = 'sample' instead of fetched user children from user.children = usrChild, also tried usrArr[index].children = 'sample'
What I am trying to implement:
using a userID, I will fetch its children, now for each children i will fetch their children until there are no children left.
function UserProfile.getUserChildren returns a promise with data consisting of all the children.
Now, we iterate each children and fetch their children
Output Expected Visually:
Programatically What I am expecting:
[
{
text: {
userID: 1
name: 'Mike'
},
children:[
{
text: {
userID: 2
name: 'John'
},
children [
{
text: {
userID: 4
name: 'Hero'
},
children []
}
]
},
{
text: {
userID: 3
name: 'Kelvin'
},
children []
}
]
}
]
Code in Node JS:
let UserProfile = require('./UserProfile');
// Love Dad 83273010
let userID = 51405009;
var allDistributors = Array();
rootUser = new Object();
rootUser.text = {userID:userID,name:'Root'};
rootUser.children = [];
function getUserTree(userID){
return new Promise( (resolve,reject) => {
/*
* UserDownline Return User child Array
* Format:
* [
* { text: {
* userID: 45
* name: 'Mike'
* },
* children:[]
* }
* ]
*
*/
UserProfile.getUserChildren(userID).then(async (data) => {
if(data.length > 0){
rootUser.children = data;
/*
* Iterating each user to fetch and assign its child
*/
await rootUser.children.forEach(async (user,index,usrArr) => {
user.children = 'sample'
await getUserTree(user.text.title).then( async(usrChild) => {
/*
Assigning child to root user
*/
usrArr[index].children = usrChild; // STILL NOT ABLE TO ASSIGN VALUE and return user.children as 'sample'
}).then(resolve(rootUser));
});
//resolve(rootUser)
//console.log(rootUser)
//return Promise.all(rootUser);
}else
resolve([]); // return empty child when no child exist
});
//return rootUser.children;
//console.log(rootUser);
});
}
//console.log(JSON.stringify(rootUser));
getUserTree(userID).then((data) => {
console.log(JSON.stringify(data));
});

As Jaromanda X pointed out, rootUser.children.foreach returns undefined instead of a promise. Consider using rootUser.children.map instead. That returns an array of Promises that you can await on using Promise.all, which returns a promise of an array. Since map does not modify the original array, you would need to assign rootUser.children to the awaited result of Promise.all

Related

Get value of parent Object

Is there any way to get a key-value from an object's parent object? In the example below, I want to combine urlParent with section:
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
subItems: [
{
subId: 1,
name: 'Project 1',
section: '#project1',
get url() {
//output /home#project1
}
}
]
}
];
console.log(linkItems[0].subItems[0].url) // /home#project1;
You cannot reference an Object parent that way (Object(value) ← Array ← Object), and not even from an Object's parent Object.
What you can do instead is:
Create two Classes, one for the parent and one for the child.
When adding a child to the parent, just make a "linked list", by referencing the parent's this to the created child item parent property
class Child {
constructor(data) {
Object.assign(this, data);
}
get url() {
return this.parent.urlParent + this.section
}
}
class Parent {
constructor(data) {
Object.assign(this, data);
this.subItems = [];
}
addChild(item) {
this.subItems.push(new Child({...item, parent: this}));
}
}
// Example:
const parent = new Parent({id:1, name:"Home", urlParent:"/home"});
parent.addChild({subId:1, name:"Project 1", section:"#project1"});
console.log(parent.subItems[0].url) // /home#project1;
But hey! Nodes and trees
Your original idea and the above use too much complexity.
What I'd suggest is to treat all parent, child, whatever, as Page Nodes.
class Page {
constructor(data) {
Object.assign(this, data);
this.children = {};
}
addChild(page) {
page.parent = this; // Linked to parent!
this.children[page.id] = page;
}
get url() {
// Generate full URI by recursing the parents tree
return this.parent ? `${this.parent.url}/${this.slug}` : this.slug;
}
}
// Example:
// 1. Create pages:
const pageRoot = new Page({id:1, name:"Home page", slug:""});
const pageProj = new Page({id:3, name:"All projects", slug:"projects"});
const pageWebs = new Page({id:4, name:"Websites", slug:"websites"});
const pageStOv = new Page({id:6, name:"Stack Overflow", slug:"so"});
const pageSpec = new Page({id:9, name:"Stack Overflow Specs", slug:"specs"});
// 2. Create Tree:
pageRoot.addChild(pageProj);
pageProj.addChild(pageWebs);
pageWebs.addChild(pageStOv);
pageStOv.addChild(pageSpec);
// 3. Test
console.log(pageRoot.url); // "";
console.log(pageProj.url); // "/projects";
console.log(pageSpec.url); // "/projects/websites/so/specs";
console.log(pageRoot);
const linkItems = [
{
id: 1,
name: 'Home Page',
urlParent: '/home',
get subItems(){
console.log(this.name);
return ([
(() => {
console.log(this);
return {
subId: 1,
name: 'Project 1',
section: '#project1',
urlParentFromOuterScope: () => {
return this.urlParent;
},
sectionString(){
return this.section;
},
url(){
console.log('url', this);
return this.urlParentFromOuterScope() + this.sectionString();
}
}
})()
])
}
}
];
const subItems = linkItems[0].subitems;
console.log(linkItems[0].subItems[0].url());
Please feel free to remove the unnecessary 'console.log's after you understand the approach.
I took the liberty of adding a few methods.
This is a tricky one and has to do with the scope of this in array functions.
P.S.: I guess this can be simplified.

How to find if an object exist in an array [array is coming from localstorage]

I created an empty array in localStorage
localStorage.setItem('items', JSON.stringify([]))
and then fetching that empty array like this :
let fetchedItems = JSON.parse(localStorage.getItem('items'));
After fetching the Array I want to append some objects in the same.
Array of Objects
let objects = [
{
id: '37f60b13-bb3a-4919-beff-239207745343',
body: '1',
},
{
id: '26c5b0fa-b15f-4a50-9a56-5880727a8020',
body: '2',
},
{
id: '37f60b13-bb3a-4919-beff-239207745343',
body: '1',
},
];
The first and last object have same id
Right now what I am doing is, as I don't want to save or append duplicate objects (by id key) in array/localstorage:
function saveItemsInLocalStorage(item) {
let items;
if (localStorage.getItem('items') === null) {
items = [];
} else {
items = JSON.parse(localStorage.getItem('items'));
}
items.push({
id: item.id,
body: item.body,
});
localStorage.setItem('items', JSON.stringify(items));
}
objects.forEach((object) => {
fetchedItems.forEach((fetchedItem) => {
if (fetchedItem.id !== object.id) {
saveItemsInLocalStorage(object)
}
});
});
The above code is not working. I have also tried reduce method.
Note initially array is empty
Let us take an example and try understanding , how you can do it with your code :
let obj = {name:"user",id:1};
let arr = [{name:"user",id:2},{name:"user",id:3}];
let present = false ;
arr.map(val=>{
if(JSON.stringify( {...val})===JSON.stringify({...obj}) )
present = true ;
})
if(present)console.log("The object is present")
else console.log("The object is not present");

saveData method saves twice

I am building a React app that includes one separate component for CRUD functionality of Products and another separate component for CRUD functionality of Suppliers.
I am using the same saveData method for both components (the Create functionality of CRUD.. that is triggered when the User presses Save after filling in the input fields of Product or Supplier). The saveData method is located in a central ProductsAndSuppliers.js file that is available to both the Products and Supplier components.
In both of the Product & Supplier components, there is a table showing the Products or Suppliers already present as dummy data.
I made a button at the bottom of each page to add a new Product or Supplier... depending on which tab the user has selected on the left side of the screen (Product or Supplier).
Since I am using the same saveData method in both cases, I have the same problem whenever I try to add a new Product or Supplier to each respective table after filling out the input fields. My new Product or Supplier is added.. but twice and I can't figure out why.
I have tried using a spread operator to add the new item to the collection but am having no success:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState((collection) => {
return { ...collection, item }
})
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
Here is my original saveData method that adds the new Product or Supplier, but twice:
saveData = (collection, item) => {
if (item.id === "") {
item.id = this.idCounter++;
this.setState(state => state[collection]
= state[collection].concat(item));
} else {
this.setState(state => state[collection]
= state[collection].map(stored =>
stored.id === item.id ? item : stored))
}
}
my state looks like this:
this.state = {
products: [
{ id: 1, name: "Kayak", category: "Watersports", price: 275 },
{ id: 2, name: "Lifejacket", category: "Watersports", price: 48.95 },
{ id: 3, name: "Soccer Ball", category: "Soccer", price: 19.50 },
],
suppliers: [
{ id: 1, name: "Surf Dudes", city: "San Jose", products: [1, 2] },
{ id: 2, name: "Field Supplies", city: "New York", products: [3] },
]
}
There are issues with both of your implementations.
Starting with the top one:
// don't do this
this.setState((collection) => {
return { ...collection, item }
})
In this case, collection is your component state and you're adding a property called item to it. You're going to get this as a result:
{
products: [],
suppliers: [],
item: item
}
The correct way to do this with the spread operator is to return an object that represents the state update. You can use a computed property name to target the appropriate collection:
this.setState((state) => ({
[collection]: [...state[collection], item]
})
)
* Note that both this and the example below are using the implicit return feature of arrow functions. Note the parens around the object.
In the second code sample you're
mutating the existing state directly which you should not do.
returning an array instead of a state update object.
// don't do this
this.setState(state =>
state[collection] = state[collection].concat(item)
);
Assignment expressions return the assigned value, so this code returns an array instead of an object and I'd frankly be surprised if this worked at all.
The correct implementation is the same as above except it uses concat instead of spread to create the new array:
this.setState(state => ({
[collection]: state[collection].concat(item)
})
);
needlessly fancy, arguably silly id generators:
const nextId = (function idGen (start = 100) {
let current = start;
return () => current++;
})(100);
console.log(nextId()); // 100
console.log(nextId()); // 101
console.log(nextId()); // 102
// ----------------
// a literal generator, just for fun
const ids = (function* IdGenerator(start = 300) {
let id = start;
while (true) {
yield id++;
}
})();
console.log(ids.next().value); // 300
console.log(ids.next().value); // 301
console.log(ids.next().value); // 302

Climb and append nested objects/array

I am trying to understand how to recursively append a branching structure to an object.
I am trying to append children to an empty JSON object that when built out should look like the following.
nodeStructure: {
text: { name: "Parent node" },
children: [
{
text: { name: "First child" },
children: [
{
text: {name: "Grandchild"}
}
]
},
{
text: { name: "Second child" }
}
]
}
Here is the most succinct version of that code.
trackFraud = async (fraudID) => {
var root = chart_config.nodeStructure = newNode(fraudID);
await fraudClimb(root, 1);
var my_chart = new Treant(chart_config);
function newNode(node) { return {text:{name:"fraud " + node}}; }
async function fraudClimb(root, fraudID) {
var frauds = await findFraudByFromID.call(this, fraudID); // returns an array of "integers"
if (frauds.length == 0) return;
var children = root.children = [];
for (var i = 0; i < frauds.length; i++) {
children.push(newNode(frauds[i]));
fraudClimb(children[i], frauds[i]);
}
}
}
Now I am trying to wrap my head around how to traverse, or in this case append to, a structure that alternates every other level between arrays and objects
I guess the real question is how to pass an object around recursively and append to that original object.
I see two issues in your code:
The first call to fraudClimb ignores the fraudID parameter that is available. Instead of:
await fraudClimb(root, 1);
I think you need:
await fraudClimb(root, fraudID);
The recursive call to fraudClimb is not awaited, yet you need the asynchronous operation to complete before you go on. So change this:
fraudClimb(children[i], frauds[i]);
to:
await fraudClimb(children[i], frauds[i]);

JS: Generate objects, which are connected dynamically via parent field

I need to generate some objects, which represents DB documents and I need to 'connect' them via parent reference. The objects are pretty simple.
The main problem for me is to define the input
The objects should be connected in a variable way: I would like to call a function with some parameters to get the result data.To make it a bit easier to understand here an example.
The structure of the needed result data could look like this:
- main
- group
- group
- item
- item
- item
- main
So for this the result output should be:
[
{ _id: 'main1ID' },
{ _id: 'group1ID', parent: 'main1ID', type: 'group' },
{ _id: 'group2ID', parent: 'main1ID', type: 'group' },
{ _id: 'item1ID', parent: 'group1ID', type: 'item' },
{ _id: 'item2ID', parent: 'group1ID', type: 'item' },
{ _id: 'item3ID', parent: 'main1ID', type: 'item' },
{ _id: 'main2ID' },
]
As you can see a main element can have group or items as children or even have no child.
Groups can also have no child at all or items as children.
So I tried to start with a basic function, but I even don't know how to define the parameters to get this function working dynamically :-(
generateContent(2, 2, 3) could create two main object, two groups and three items, but there is no information, how they should be connected to each other. And this is the main problem for me...
function generateContent (main = 0, group = 0, item = 0) {
const mainDocs = []
for (var i = 0; i <= main; i++) {
mainDocs.push({
_id: Random.id()
})
}
// do similiar thing for group and item
// return everything
}
You would need to encode in your input the relations. It is not enough to indicate the number of each type of node. There are several encodings you could think of. A very concise one would be a string pattern like the following:
m(gg(ii)i)m
Here each letter represents the type of node to produce, and the parentheses make clear how they should relate to each other.
Here is a function that would parse such a string into the final array:
function generateContent (pattern) {
const mainDocs = [],
parents = [],
count = { m:0, g:0, i:0 };
let obj, parent;
for (let ch of pattern) {
if (ch === '(') {
parents.push(parent);
parent = obj._id;
} else if (ch === ')') {
parent = parents.pop();
} else {
let type = { m: 'main', g: 'group', i: 'item' }[ch];
obj = {
_id: type + (++count[ch]) + 'ID'
};
if (parent) obj.parent = parent;
if (ch !== 'm') obj.type = type;
mainDocs.push(obj);
}
}
return mainDocs;
}
// Example:
const result = generateContent('m(gg(ii)i)m');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources