React: populating select option tab from nested data set - javascript

Im trying to create a React functional component that will populate a select option tags from nested data but having trouble getting it to work.
Here's the working copy of the code at codesandbox: sample project
My data looks like this:
{
channels: [
{
id: "1878",
name: "Audio/Video",
depth: 0,
children: [
{
id: "1885",
name: "Comedy",
depth: 1,
children: []
},
{
id: "1886",
name: "Opera",
depth: 1,
children: []
},
{
id: "1894",
name: "Lifestyle",
depth: 1,
children: [
{
id: "1895",
name: "Fashion",
depth: 2
},
{
id: "1896",
name: "Fitness",
depth: 2
}
]
}
]
},
...
]
}
I created a function that console log the result as I expected. But having trouble translating this to a React functional component.
Here's the function that prints to console (output is what I wanted but as option tabs):
DeepIteratorTree(json);
function DeepIteratorTree(target) {
const channels = target.channels;
// console.log(channels)
return channels.map((el) => {
if (el.depth === 0) {
console.log(el.name);
if (el.children.length !== 0) {
el.children.map((ch) => {
console.log(
`${" ".repeat(Number.parseInt(ch.depth, 10))}${" "}${ch.name}`
);
if (ch.children.length !== 0) {
ch.children.map((chch) => {
console.log(
`${" ".repeat(Number.parseInt(chch.depth, 10))}${" "}${
chch.name
}`
);
return null;
});
}
return null;
});
}
}
return null;
});
}
console out put:
Audio/Video
Comedy
Opera
Lifestyle
Fashion
Fitness
Reader
News
Publishing
Books
Magazines
Gallery
Arts
Shows
Following function works to populate the first level option tags but but not the next level. I have also tried using nested ternary statements but unable to make it work.
function DeepIteratorTree2(target) {
const channels = target.json.channels;
return channels.map((el) => {
if (el.depth === 0) {
return <option key={el.name}>{el.name}</option>;
}
if (el.children.length !== 0) {
return el.children.map((ch) => {
return <option key={ch.name}>{ch.name}</option>;
});
}
return null;
});
// return null
}
Thank you in advance for your time.

Instead of trying to deep iterate and map DOM Nodes yourself in React, I think you can benefit from flattening your json.channels array first and then just simply having a single map over that flattened channels array to render select options like so :-
import "./styles.css";
export default function App() {
const channels = flatten(json.channels);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<select>
<ChannelsSelector channels={channels} />
</select>
</div>
);
}
const json = {
channels: [
{
id: "1878",
name: "Audio/Video",
depth: 0,
children: [
{
id: "1885",
name: "Comedy",
depth: 1,
children: []
},
{
id: "1886",
name: "Opera",
depth: 1,
children: []
},
{
id: "1894",
name: "Lifestyle",
depth: 1,
children: [
{
id: "1895",
name: "Fashion",
depth: 2
},
{
id: "1896",
name: "Fitness",
depth: 2
}
]
}
]
},
{
id: "1879",
name: "Reader",
depth: 0,
children: [
{
id: "1902",
name: "News",
depth: 1,
children: []
},
{
id: "1903",
name: "Publishing",
depth: 1,
children: [
{
id: "1904",
name: "Books",
depth: 2
},
{
id: "1905",
name: "Magazines",
depth: 2
}
]
}
]
},
{
id: "1880",
name: "Gallery",
depth: 0,
children: [
{
id: "1908",
name: "Arts",
depth: 1,
children: []
}
]
},
{
id: "1884",
name: "Shows",
depth: 0,
children: []
}
]
};
function flatten(channels) {
const output = [];
function process(channels) {
for (let index = 0; index < channels.length; index++) {
const channel = channels[index];
output.push(channel.name);
if (channel.children && channel.children.length > 0) {
process(channel.children);
}
}
}
process(channels);
return output;
}
function ChannelsSelector({ channels }) {
return channels.map((channel) => {
return <option key={channel}>{channel}</option>;
});
}
Here is the forked version :-
Note :- There could be a better declarative implementation of flatten function. The above has an imperative one. Following is a declarative one :-
function flatten(channels) {
return channels.reduce((output,channel)=>{
output.push(channel.name);
if(channel.children?.length>0)
{
let children = flatten(channel.children);
output = output.concat(children)
// or output.push(...children)
}
return output;
},[])
}

You are checking
el.depth === 0
Instead of
el.children.length === 0
In this part:
return channels.map((el) => {
if (el.depth === 0) {
return <option key={el.name}>{el.name}</option>;
}
if (el.children.length !== 0) {
return el.children.map((ch) => {
return <option key={ch.name}>{ch.name}</option>;
});
}
return null;
});
Working example:
Working Example
Hope that is what your are trying to achieve...

Related

Being able to remove duplicate keys from an array of objects

I have a question about how I can delete the existing elements, for example, in my case "Tallas" is repeated, could you please help me? Thank you very much to those who are willing to help me to solve this problem
const data =
[ { atributos: { Tallas: [{ id: 0, name: 'XS' }, { id: 1, name: 'S' }] }}
, { atributos: { Calzado: [{ id: 0, name: '10' }, { id: 1, name: '9.5' }] }}
, { atributos: { Tallas: [{ id: 0, name: 'XS' }] }}
]
The idea is to have this json format with the last "Tallas" since it is the last one that I added through my dynamic form.
const expected =
[{ atributos: { Calzado: [{ id: 0, name: '10' }, { id: 1, name: '9.5' }] }}
, { atributos: { Tallas: [{ id: 0, name: 'XS' }] }}
]
How do I do this is there a way to do it, I've tried with filter plus the findindex but I can't get to eliminate the repetition of the json res= new.filter((arr, index, self) => index === self.findIndex( (t) => (t.attributes === arr.attributes )))
To unique the array of objects, we can use the Javascript Set module, if the array has complex nested objects, we can stringify each object before creating new Set data. this below function will unique the array of complex objects.
function unique_array(array = []) {
const newSetData = new Set(array.map((e) => JSON.stringify(e)));
return Array.from(newSetData).map((e) => JSON.parse(e));
}
this is a function that takes an array and return the same array but delete every duplicated item
function removeDuplicates(arr) {
return arr.filter((item,
index) => arr.indexOf(item) === index);
}
I didn't understant the part written in spanish so I hope this is what you are looking for
This is a solution specific to your question. this is not a generic solution.
const data = [
{
atributos: {
Tallas: [
{ id: 0, name: "XS" },
{ id: 1, name: "S" },
],
},
},
{
atributos: {
Calzado: [
{ id: 0, name: "10" },
{ id: 1, name: "9.5" },
],
},
},
{
atributos: {
Tallas: [
{ id: 0, name: "XS" },
{ id: 1, name: "S" },
],
},
},
];
function uniqueArray(array) {
const resultObject = array.reduce((acc, eachValue) => {
let keys = Object.keys(eachValue.atributos);
keys.forEach((eachKey) => {
if (!acc[eachKey]) {
acc[eachKey] = [];
}
let list = eachValue["atributos"][eachKey].map(
(each) => each.id + "-" + each.name
);
acc[eachKey].push(...list);
});
return acc;
}, {});
const resultArray = Object.keys(resultObject).reduce((acc, each) => {
let setData = Array.from(new Set(resultObject[each]));
acc.push({
atributos: {
[each]: setData.map((e) => {
return { id: e.split("-")[0], name: e.split("-")[1] };
}),
},
});
return acc;
}, []);
return resultArray;
}
const result = uniqueArray(data)
console.log("result ", JSON.stringify(result, null, 2));

Group array of objects by multiple nested values

I have an array of objects which presents tasks. These tasks are categorized (primary / secondary category).
let tasks = [
{
id: 1,
name: 'Cleanup desk',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 2,
name: 'Surfaces'
}
},
{
id: 2,
name: 'Cleanup office floors',
primary_category: {
id: 1,
name: 'Indoor'
},
secondary_category: {
id: 3,
name: 'Ground'
}
},
{
id: 3,
name: 'Water plants',
primary_category: {
id: 2,
name: 'Outdoor'
},
secondary_category: {
id: 3,
name: 'Irrigation'
}
}
];
I now try to create a categories accordion in my frontend and therefore need to group my array differently. The structure should look like:
1) primary category
> secondary category
> tasks
> secondary category
> tasks
2) primary category
> secondary category
> tasks
Therefore I'm trying to achieve a structure similar to this:
let tasks_categorized = [
{
id: 1,
name: 'Indoor',
secondary_categories: [
{
id: 2,
name: 'Surfaces',
tasks: [
{
id: 1,
name: 'Cleanup desk'
}
]
},
{
id: 3,
name: 'Ground',
tasks: [
{
id: 2,
name: 'Cleanup office floors'
}
]
}
]
},
{
id: 2,
name: 'Outdoor',
secondary_categories: [
{
id: 3,
name: 'Irrigation',
tasks: [
{
id: 3,
name: 'Water plants'
}
]
}
]
}
];
I tried using groupBy by lodash but this does not allow grouping by multiple nested key-value pairs. Does anybody know an approach to solve this?
Thank you in advance!
The following provided approach is going to achieve the expected result within a single reduce cycle without any further nested loops.
It does so by implementing a reducer function which creates and/or aggregates at time a prioritized category task while iterating another task array. But most importantly it keeps track of a task item's related primary and secondary categories via a Map based lookup. This lookup reference together with a result array are properties of this function's return value which has to be partly provided as the reduce method's initial value as follows ... { result: [] }.
function createAndAggregatePrioritizedCategoryTask(
{ lookup = new Map, result }, item
) {
const { primary_category, secondary_category, ...taskRest } = item;
const { id: primaryId, name: primaryName } = primary_category;
const { id: secondaryId, name: secondaryName } = secondary_category;
const primaryKey = [primaryId, primaryName].join('###');
const secondaryKey = [primaryKey, secondaryId, secondaryName].join('###');
let primaryCategory = lookup.get(primaryKey);
if (!primaryCategory) {
// create new primary category item.
primaryCategory = {
id: primaryId,
name: primaryName,
secondary_categories: [],
};
// store newly created primary category reference in `lookup`.
lookup.set(primaryKey, primaryCategory);
// push newly created primary category reference to `result`.
result.push(primaryCategory);
}
let secondaryCategory = lookup.get(secondaryKey);
if (!secondaryCategory) {
// create new secondary category item.
secondaryCategory = {
id: secondaryId,
name: secondaryName,
tasks: [],
};
// store newly created secondary category reference in `lookup`.
lookup.set(secondaryKey, secondaryCategory);
// push newly created secondary category reference into the
// `secondary_categories` array of its related primary category.
primaryCategory
.secondary_categories
.push(secondaryCategory);
}
// push the currently processed task-item's rest-data as
// item into the related secondary category's `task` array.
secondaryCategory
.tasks
.push(taskRest);
return { lookup, result };
}
let tasks = [{
id: 1,
name: 'Cleanup desk',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 2, name: 'Surfaces' },
}, {
id: 2,
name: 'Cleanup office floors',
primary_category: { id: 1, name: 'Indoor' },
secondary_category: { id: 3, name: 'Ground' },
}, {
id: 3,
name: 'Water plants',
primary_category: { id: 2, name: 'Outdoor' },
secondary_category: { id: 3, name: 'Irrigation' },
}];
const { result: tasks_categorized } = tasks
.reduce(createAndAggregatePrioritizedCategoryTask, { result: [] });
console.log({ tasks_categorized });
.as-console-wrapper { min-height: 100%!important; top: 0; }
You could take a dynamic approach with an array of arrays with functions and keys for the nested arrays.
const
tasks = [{ id: 1, name: 'Cleanup desk', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 2, name: 'Surfaces' } }, { id: 2, name: 'Cleanup office floors', primary_category: { id: 1, name: 'Indoor' }, secondary_category: { id: 3, name: 'Ground' } }, { id: 3, name: 'Water plants', primary_category: { id: 2, name: 'Outdoor' }, secondary_category: { id: 3, name: 'Irrigation' } }],
groups = [
[o => o, 'primary category'],
[o => o.primary_category, 'secondary category'],
[o => o.secondary_category, 'tasks']
],
result = tasks.reduce((r, o) => {
groups.reduce((parent, [fn, children]) => {
const { id, name } = fn(o);
let item = (parent[children] ??= []).find(q => q.id === id)
if (!item) parent[children].push(item = { id, name });
return item;
}, r);
return r;
}, {})[groups[0][1]];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Remove duplicate elements of an array, in an object of arrays, dynamically

I have checked other solutions but none fit the criterion of my problem
This solution does not have the ability to dynamically check each node
Problem summarized
I wish to create an algorithm that is able to check an object that has nodes of different data types, for duplicated objects in nodes that are specifically of the datatype array.
I have the following dataset:
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 2 }, { id: 3 }],
};
I wish to be able to dynamically check which of the objects (or nodes? and the algo has to recognize that it is an array) has duplicates, and reduce them to be in this form:
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 3 }],
};
EDIT
The algorithm needs to be able to handle a dynamic number of nodes (example 1), however , the duplicates will only happen 1 level down (Thanks for pointing out).
example 1 (there is 1 less node here ) :
task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 2 }],
};
Here is my proposed solution to remove duplicate elements from any array in the task object:
const uniq = array => {
const map = {};
const result = [];
for (let i = 0; i < array.length; i++) {
// since elements can be objects, need to do a deep comparison.
const element = JSON.stringify(array[i]);
if (map[element] === undefined) {
map[element] = true;
result.push(array[i]);
}
}
return result;
}
const task = {
content: "lorem....",
customer: [
{ id: 1, name: "hello" },
{ id: 2, name: "sup" },
],
end: "2020-08-13 10:09:48",
project: [{ id: 1 }, { id: 1 }, { id: 2 }],
vendor: [{ id: 2 }, { id: 2 }, { id: 3 }],
};
for (const key in task) {
if (Array.isArray(task[key])) {
task[key] = uniq(task[key])
}
}
console.log('deduped:', task);

Filter nested object from parent object

I have a parent object with a child array of objects nest underneath. Each object contains an id key with a unique value. A filter function needs to search the parent object for an id, if it does not equal the given id then recursively search through nested objects for the id until it is found. Once the object with the given key is found the remove and return the updated myObject.
The structure looks as followed:
let myObject = {
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'you',
children: [{
key: 5,
name: 'are',
children: []
}]
},
{
key: 6,
name: 'having',
children: [{
key: 7,
name: 'fun',
children: []
}]
}
]
}
]
}
let given = 4;
if (myObject.key !== given) {
myObject = searchChild(myObject, given)
} else {
myObject = {}
}
function searchChild(parent, given) {
parent.children.map(child => {
return child.children.filter(item => {
if (item.key === given) return item;
else if (item.key !== given
&& child.children.length > 0
&& child.children != undefined) {
searchChild(child.children, given);
}
})
})
}
Currently, I am receiving a type error when running the recursive function.
The output should look like where the keys are updated to the new order in tree:
{
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'having',
children: [{
key: 5,
name: 'fun',
children: []
}]
}
]
}
]
}
Here is function you can call for your object
function searchInChild(parent,key){
parent.children = parent.children.filter((c)=>{
if(key == c.key ){
result = c;
return false;
}
return true;
});
if(result == null){
for(c in parent.children){
searchInChild(parent.children[c],key);
}
}
}
Where, you can simply pass searchInChild(myObject,key) & make result global variable.
You pass child.children but you have to pass child you already iterate through children in the function.
let myObject = {
key: 1,
name: 'hello',
children: [
{
key: 2,
name: 'world',
children: []
},
{
key: 3,
name: 'hope',
children: [
{
key: 4,
name: 'you',
children: [{
key: 5,
name: 'are',
children: []
}]
},
{
key: 6,
name: 'having',
children: [{
key: 7,
name: 'fun',
children: []
}]
}
]
}
]
}
let given = 4;
if (myObject.key !== given) {
myObject = searchChild(myObject, given)
} else {
myObject = {}
}
function searchChild(parent, given) {
if(parent && parent.children) {
parent.children.map(child => {
return child.children.filter(item => {
if (item.key === given) return item;
else if (item.key !== given
&& child.children.length > 0
&& child.children != undefined) {
searchChild(child, given);
}
})
})
}
}

How to build the path to each node in a tree recursively - JavaScript?

My data structure will look like this:
var tree = [
{
id: 1,
children: []
}, {
id: 2,
children: [
{
id: 3,
children: []
}
]
}
];
There can be any number of nodes or children on one branch.
My goal is to build a path to every node.
For example id: 3 will have a path of 1 > 2 > 3
id: 2 will have a path of 1 > 2
I want to run my tree through the algorithm so it will be modified like this:
var tree = [
{
id: 1,
path: [1],
children: []
}, {
id: 2,
path: [2],
children: [
{
id: 3,
path: [2, 3],
children: []
}
]
}
];
I have written an algorithm that will visit all of the nodes in the tree:
https://plnkr.co/edit/CF1VNofzpafhd1MOMVfj
How can I build the path to each node?
Here is my attempt:
function traverse(branch, parent) {
for (var i = 0; i < branch.length; i++) {
branch[i].visited = true;
if (branch[i].path === undefined) {
branch[i].path = [];
}
if (parent != null) {
branch[i].path.push(parent);
}
if (branch[i].children.length > 0) {
traverse(branch[i].children, branch[i].id);
}
}
}
Beside the unclear taking of not directly involved parents, you could store the path as arrray and take it for each nested iteration.
function iter(path) {
path = path || [];
return function (o) {
o.path = path.concat(o.id);
if (o.children) {
o.children.forEach(iter(o.path));
}
}
}
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
tree.forEach(iter());
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You made a mistake
Your root node is an array, but all other nodes are objects.
This makes your program inconsistent and needlessly complex to handle the root node difference – the solution is to stop writing data using literals – you're bound to make mistakes like you did above
Instead, just make some simple data constructors and your complexities vanish into thin air
const Node = (id, ...children) =>
({ id, children })
const PathNode = (id, path, ...children) =>
({ id, path, children })
const addPaths = ({id, children}, acc = []) =>
PathNode (id, acc, children.map (child =>
addPaths (child, [...acc, id])))
const tree =
Node (0, Node (1),
Node (2, Node (3)))
console.log (tree)
// { id: 0, children: [
// { id: 1, children: [ ] },
// { id: 2, children: [
// { id: 3, children: [ ] } ] } ] }
console.log (addPaths (tree))
// { id: 0, path: [ ], children: [
// { id: 1, path: [ 0 ], children: [ ] },
// { id: 2, path: [ 0 ], children: [
// { id: 3, path: [ 0, 2 ], children: [ ] } ] } ] }
You could use reduce method to create a recursive function and pass the previous path values in recursive calls as an array of id's.
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
function getPaths(data, prev = []) {
return data.reduce((r, { id, children }) => {
const o = { id, children, path: [...prev, id] }
if (children) {
o.children = getPaths(children, o.path)
}
r.push(o)
return r
}, [])
}
console.log(getPaths(tree))

Categories

Resources