React / Redux - how to render/update deeply nested sate - javascript

I'll get from the API a deeply nested state - e.g.:
[{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": []
}]
}]
}]
}]
(Please ignore duplicate ids etc)
Now here are the requirements:
I need to properly render this
e.g.
<div>
text
<div>
text
</div>
</div>
I need to be able to update the nested state within redux store
This list can be huge - like at least 3k items (which theoretically works fine)
What I tried:
Having everything unnested:
Rendering is very complicated (with parentId)
Maintaining the structure is difficult (need to flatten and unflatten it) -> this costs a lot of performance
Having everything nested:
Updating the store is impossible without "cheating" in react -> manipulating the state directly
What can be a solution to this? What should be the architecture

Something like immutability-helper will probably be of use to you here.
const state = [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": []
}]
}]
}]
}];
const newState = update(state, {
0: {
children: {
0: {
children {
0 : {
children: {
0: {
"id": { $set: 2}
}
}
}
}
}
}
};
return newState;
[{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 1,
"text": "test",
"children": [{
"id": 2,
"text": "test",
"children": []
}]
}]
}]
}];
The 0s here could be replaced with some indexes in your payload; I just used 0 here as the example arrays all have only 1 element in them. This is quite deeply nested though so as the comments pointed out, any flattening you can do will make the updates easier.

Related

How can I add the result of a recursive function in a json?

I have this json:
[
{
"question": "1.1",
"level": 1,
"id": 4,
"answers": [
{
"text_answer": "NO",
"questions": [
{
"question": "1.1.1",
"level": 2,
"id": 3,
"answers": []
}
]
},
{
"text_answer": null,
"questions": [
{
"question": "1.1.2",
"level": 2,
"id": 2,
"answers": [
{
"text_answer": "SI",
"questions": [
{
"question": "1.1.2.1",
"level": 3,
"id": 1,
"answers": []
}
]
}
]
}
]
}
]
}
]
this json is dynamic and can have n amount of levels under the fields answers and questions that is why it is necessary to go through them with a recursive function
I want to get this output:
[
{
"question": "1.1",
"level": 1,
"id": 4,
"children": [
{
"question": "1.1.1",
"text_answer": "NO",
"level": 2,
"id": 3,
"children": []
},
{
"question": "1.1.2",
"text_answer": null,
"level": 2,
"id": 2,
"children": [
{
"question": "1.1.2.1",
"text_answer": "SI",
"level": 3,
"id": 1,
"children": []
}
]
}
]
}
]
the fields answers and questions no longer exist, they are renamed children and the content of both is unified.
I think this recursive function should do a double foreach, but I don't know how to do it.
function format(d) {
if (d.respuestas) {
d.respuestas.forEach((d) => {
format;
});
}
}
format(data);
var data= [
{
"question": "1.1",
"level": 1,
"id": 4,
"answers": [
{
"text_answer": "NO",
"questions": [
{
"question": "1.1.1",
"level": 2,
"id": 3,
"answers": []
}
]
},
{
"text_answer": null,
"questions": [
{
"question": "1.1.2",
"level": 2,
"id": 2,
"answers": [
{
"text_answer": "SI",
"questions": [
{
"question": "1.1.2.1",
"level": 3,
"id": 1,
"answers": []
}
]
}
]
}
]
}
]
}
]
/* [
{
"question": "1.1",
"level": 1,
"id": 4,
"children": [
{
"question": "1.1.1",
"text_answer": "NO",
"level": 2,
"id": 3,
"children": []
},
{
"question": "1.1.2",
"text_answer": null,
"level": 2,
"id": 2,
"children": [
{
"question": "1.1.2.1",
"text_answer": "SI",
"level": 3,
"id": 1,
"children": []
}
]
}
]
}
]
*/
function format(d) {
if (d.answers) {
d.answers.forEach((d) => {
format;
});
}
}
format(data);
**note:**
I changed the previous structure that I put to the question to make myself understand better.
Here is one approach via recurssion. I've commented the code to explain what exactly is going on line by line.
function _process(item) {
let result = item // we define a copy of item -> result
// this is renaming the field 'answers' (if found)
if (item.answers) {
result.children = item.answers.map(_process) // process each item separately
delete result.answers
}
// if there is a question field, extract the first item in the array and merge the attributes with result
if (item.questions) {
result = {
...result,
..._process(item.questions[0]) // also try to process the item before merging it (to check if there are nested 'questions' or 'answers' fields)
}
delete result.questions // remove the questions field
}
return result
}
function process(data) {
return data.map(_process) // start the recurssion for top-level objects
}
// sample data to test out
const data = [{
"question": "1.1",
"level": 1,
"id": 4,
"answers": [{
"text_answer": "NO",
"questions": [{
"question": "1.1.1",
"level": 2,
"id": 3,
"answers": []
}]
},
{
"text_answer": null,
"questions": [{
"question": "1.1.2",
"level": 2,
"id": 2,
"answers": [{
"text_answer": "SI",
"questions": [{
"question": "1.1.2.1",
"level": 3,
"id": 1,
"answers": []
}]
}]
}]
}
]
}]
const result = process(data)
console.log(JSON.stringify(result, null, 2))
You're on the right track in writing a recursive function.
Where I think you're going wrong is in thinking about 'pushing' it to an array or object to store the data.
Instead, what should happen is that your recursive function returns the formatted data, and then that data is added to new json object which is sent back up the call stack.
var data = {
"name": "1.question",
"answer": "YES",
"children": [{
"name": "1.1 question",
"answer": "yes"
},
{
"name": "1.2 question",
"answer": "NO",
"children": [{
"name": "1.2.1 question",
"answer": "NO"
}]
},
{
"name": "1.3 question",
"answer": "YES",
"children": [{
"name": "1.3.1 question",
"answer": "yes",
"children": [{
"name": "1.3.1.1 question",
"answer": "YES"
}]
}]
}
]
}
function format(d) {
if (d.children) {
// if there are children you are going to recurse deeper
// use the Array.prototype.map function to _transform_ each of the children.
const formattedChildren = d.children.map(v => {
return format(v);
});
// Notice that I'm returning a new object here, as well as
// An aggregation of the already transformed data
return {
data: "new formatted parent node data goes here",
children: formattedChildren
};
} else { // _always_ have a check for 'is it a leaf node'.
// If it's a leaf node node, just format it.
//Notice that I'm returning a new object here
return {
data: "new formatted leaf node data goes here"
};
}
}
console.log(format(data));
I don't know what you are trying to achieve, so I've left this blank. But this is the template for how you would recursively traverse and transform a nested object like this.
(Note that you don't need to have a data key, I've just put that in as a place holder. It looks like you want name and answered keys.
It works for me:
// here is your JSON
var json = [{
"question": "1.1",
"level": 1,
"id": 4,
"answers": [{
"text_answer": "NO",
"questions": [{
"question": "1.1.1",
"level": 2,
"id": 3,
"answers": []
}]
},
{
"text_answer": null,
"questions": [{
"question": "1.1.2",
"level": 2,
"id": 2,
"answers": [{
"text_answer": "SI",
"questions": [{
"question": "1.1.2.1",
"level": 3,
"id": 1,
"answers": []
}]
}]
}]
}
]
}]
// main
function change(branch) {
// the first level has a bit different structure
try {
if (branch.level == 1) {
return {
question: branch.question,
level: branch.level,
id: branch.id,
children: change(branch.answers)
}
}
} catch (e) {}
// next levels
var new_branch = [];
for (var i = 0; i < branch.length; i++) {
new_branch.push({
question: branch[i].questions[0].question,
text_answer: branch[i].text_answer,
level: branch[i].questions[0].level,
id: branch[i].questions[0].id,
children: [change(branch[i].questions[0].answers)].flat(1)
});
}
return new_branch;
}
// output
console.log(JSON.stringify(change(json[0])));

Vue: How to bind checked value from array of objects

For the last 10 hours straight i'm struggling with a seemingly simple issue, i hope someone can assist me.
Situation:
data: {
user: {
email: 'test#test.com',
has_items: [
{ "id": 19, "userId": 63, "projectId": 1, "project": { "id": 1, "titel": "Project1" } },
{ "id": 20, "userId": 63, "projectId": 2, "project": { "id": 2, "titel": "Project2" } }
]
},
items: [
{ "label": "Project1", "titel": "Project1", "projectId": 1 },
{ "label": "Project2", "titel": "Project2", "projectId": 2 },
{ "label": "Project3", "titel": "Project3", "projectId": 3 }
]
}
However, i can't seem to bind the has_items projectId to the checkbox checked state. I'm pretty confident it's one of those cases where messing around made things worse, and a simple solution is overlooked.
I've added an example fiddle: http://jsfiddle.net/ebzgr4c3/31/
Hope someone can point out the error i made, and make this work :)
Thanks!
Because the elements in user.has_items have some extra formatting compared to the elements in items, I don't think v-model is really an option here. You'll need space to make some sort of translation from item to has_item happen between the time the checkbox is checked and the time the project is pushed onto the user's array.
You can make that space by trading v-model for a more manual option: events. In my example here, I used the change event to trigger a method that adds or removes the project, based on whether the checkbox is checked or unchecked.
I set the checkbox's initial value with another method. This method checks whether the checkbox's associated project is in the user's array of items.
var demo = new Vue({
el: '#app',
data: {
user: {
email: 'test#test.com',
has_items: [
{ "id": 19, "userId": 63, "projectId": 1, "project": { "id": 1, "titel": "Project1" } },
{ "id": 20, "userId": 63, "projectId": 2, "project": { "id": 2, "titel": "Project2" } }
]
},
items: [
{ "label": "Project1", "titel": "Project1", "projectId": 1 },
{ "label": "Project2", "titel": "Project2", "projectId": 2 },
{ "label": "Project3", "titel": "Project3", "projectId": 3 }
]
},
methods: {
userHasItem(project) {
return this.user.has_items.find(e => e.projectId == project.projectId) != undefined;
},
updateHasItems(checked, project) {
if (checked) {
// Add item to user.has_items
// [Translation between item and has_item goes here]
this.user.has_items.push(project);
}
else {
// Remove item from user.has_items based on ID
this.user.has_items.splice(this.user.has_items.findIndex(e => e.projectId == project.projectId), 1);
}
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<div v-for="project in items" :key="project.projectId">
<label>{{project.titel}}</label>
<input type="checkbox" :checked="userHasItem(project)" :value="project" #change="updateHasItems($event.target.checked, project)"/>
</div>
<p>User's items</p>
{{user.has_items}}
</div>

How do I loop through nested arrays?

I have this structure below, and I want to loop through the hierarchy without missing any object.
{
"countries": [
{
"name": "Denmark",
"id": "APA1",
"children": [
{
"name": "Zealand",
"id": "APA1.1",
"parentId": "APA1",
"children": [
{
"name": "Copenhagen",
"id": "APA1.1.1",
"parentId": "APA1.1",
"children": [
{
"name": "Dublin",
"id": "ANA1",
"parentId": "APA1.1.1.1",
"hostNames": [
{
"ip": "20.190.129.1"
},
{
"ip": "20.190.129.2"
}
]
}
]
}
]
},
{
"name": "Jutland",
"id": "APA1.2",
"parentId": "APA1",
"children": [
{
"name": "Nordjylland",
"id": "APA1.2.1",
"parentId": "APA1.2",
"children": [
{
"name": "Aalborg",
"id": "APA1.2.1.1",
"parentId": "APA1.2.1",
"children": [
{
"name": "Risskov",
"id": "ANA3",
"parentId": "APA1.2.1.1",
"hostNames": [
{
"ip": "40.101.81.146"
}
]
},
{
"name": "Brabrand",
"id": "ANA4",
"parentId": "APA1.2.1.1",
"hostNames": [
{
"ip": "43.203.94.182"
}
]
}
]
}
]
}
]
}
]
}
]
}
The reason why I want to loop through the hierarchy is that I want to turn this into a flat structure. So essentially I'm gonna take every object and move it to another array which has the structure that I want. I just want to know how to access the children.
The wanted result:
"applicationGroups": [
{
"id" : "APA1",
"name": "Denmark",
},
{
"name": "Zealand",
"id": "APA1.1",
"parentId": "APA1"
},
{
"name": "Copenhagen",
"id": "APA1.1.1",
"parentId": "APA1.1"
},
{
"name": "Dublin",
"id": "ANA1",
"parentId": "APA1.1.1.1"
},
{
"name": "Jutland",
"id": "APA1.2",
"parentId": "APA1"
},
{
"name": "Nordjylland",
"id": "APA1.2.1",
"parentId": "APA1.2"
},
{
"name": "Aalborg",
"id": "APA1.2.1.1",
"parentId": "APA1.2.1"
},
{
"name": "Risskov",
"id": "ANA3",
"parentId": "APA1.2.1.1"
},
{
"name": "Brabrand",
"id": "ANA4",
"parentId": "APA1.2.1.1"
}
]
I'm a bit new to JavaScript, and I don't really know where to start, but this example that I have given is not identical to the actual one that I'm working on, so I just want the principle so I can implement it myself in my actual code.
You could take a flatMap approach for the recursive call of a flattening callback.
const
flat = ({ hostNames, children = [], ...o }) => [o, ...children.flatMap(flat)],
data = { countries: [{ name: "Denmark", id: "APA1", children: [{ name: "Zealand", id: "APA1.1", parentId: "APA1", children: [{ name: "Copenhagen", id: "APA1.1.1", parentId: "APA1.1", children: [{ name: "Dublin", id: "ANA1", parentId: "APA1.1.1.1", hostNames: [{ ip: "20.190.129.1" }, { ip: "20.190.129.2" }] }] }] }, { name: "Jutland", id: "APA1.2", parentId: "APA1", children: [{ name: "Nordjylland", id: "APA1.2.1", parentId: "APA1.2", children: [{ name: "Aalborg", id: "APA1.2.1.1", parentId: "APA1.2.1", children: [{ name: "Risskov", id: "ANA3", parentId: "APA1.2.1.1", hostNames: [{ ip: "40.101.81.146" }] }, { name: "Brabrand", id: "ANA4", parentId: "APA1.2.1.1", hostNames: [{ ip: "43.203.94.182" }] }] }] }] }] }] },
result = data.countries.flatMap(flat);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can combine the Array.flat() method and this answer to flatten objects recursively.
Using recursive functions is the faster way to accomplish that.
To get a flat structure you could use reduce method to create recursive function.
const data = {"countries":[{"name":"Denmark","id":"APA1","children":[{"name":"Zealand","id":"APA1.1","parentId":"APA1","children":[{"name":"Copenhagen","id":"APA1.1.1","parentId":"APA1.1","children":[{"name":"Dublin","id":"ANA1","parentId":"APA1.1.1.1","hostNames":[{"ip":"20.190.129.1"},{"ip":"20.190.129.2"}]}]}]},{"name":"Jutland","id":"APA1.2","parentId":"APA1","children":[{"name":"Nordjylland","id":"APA1.2.1","parentId":"APA1.2","children":[{"name":"Aalborg","id":"APA1.2.1.1","parentId":"APA1.2.1","children":[{"name":"Risskov","id":"ANA3","parentId":"APA1.2.1.1","hostNames":[{"ip":"40.101.81.146"}]},{"name":"Brabrand","id":"ANA4","parentId":"APA1.2.1.1","hostNames":[{"ip":"43.203.94.182"}]}]}]}]}]}]}
function flat(data) {
return data.reduce((r, { children, ...rest }) => {
if (children) r.push(...flat(children))
r.push(rest)
return r;
}, [])
}
const result = flat(data.countries)
console.log(result)

Most performant way to sort a deeply nested array of objects by another deeply nested array of objects

As an example - I've included a one element array that contains an object that has a Children key, which is an array of objects and each object also has its' own Children key that contains another array.
[
{
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "10",
"DisplayName": "3-4",
},
{
"Id": "1000",
"DisplayName": "5-6",
},
{
"Id": "100",
"DisplayName": "1-2",
},
]
}
]
}
]
There is a second array of objects that I would like to compare the first array of objects to, with the intention of making sure that the first array is in the same order as the second array of objects, and if it is not - then sort until it is.
Here is the second array:
[
{
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "100",
"DisplayName": "1-2",
},
{
"Id": "10",
"DisplayName": "3-4",
},
{
"Id": "1000",
"DisplayName": "5-6",
},
]
}
]
}
]
The data that this will run on can be up in the tens of thousands - so performance is paramount.
What I'm currently attempting is using a utility method to convert each element of the second array into a keyed object of objects e.g.
{
1: {
"Id": "1",
"Children": [
{
"Id": "2",
"Children": [
{
"Id": "4",
"DisplayName": "3-4",
},
{
"Id": "3",
"DisplayName": "1-2",
},
]
}
]
}
}
This allows fast look up from the top level. I'm wondering if I should continue doing this all the way down or if there is an idiomatic way to accomplish this. I considered recursion as well.
The order of the already sorted array is not based on Id - it is arbitrary. So the order needs to be preserved regardless.
Assuming same depth and all Id's exist in each level of each object use a recursive function that matches using Array#findIndex() in sort callback
function sortChildren(main, other) {
other.forEach((o, i) => {
if (o.children) {
const mChilds = main[i].children, oChilds = o.children;
oChilds.sort((a, b) => {
return mChilds.findIndex(main => main.Id === a.Id) - mChilds.findIndex(main => main.Id === b.Id)
});
// call function again on this level passing appropriate children arrays in
sortChildren(mChilds, oChilds)
}
})
}
sortChildren(data, newData);
console.log(JSON.stringify(newData, null, ' '))
<script>
var data = [{
"Id": "1",
"Children": [{
"Id": "2",
"Children": [{
"Id": "3",
"DisplayName": "1-2",
},
{
"Id": "4",
"DisplayName": "3-4",
},
]
}]
}]
var newData = [{
"Id": "1",
"Children": [{
"Id": "2",
"Children": [{
"Id": "4",
"DisplayName": "3-4",
},
{
"Id": "3",
"DisplayName": "1-2",
},
]
}]
}]
</script>

Check for Parent Child node in JSON using angular JS

I am looking for JSON parsing reference, from where I can jump to question and check for Child question based on Yes section. I didn't find anything related to check for child node check in JSON. Angular is my base framework.
some use cases :
on load show Root question,
On selection show next questions which is child of root then go one
jump to number of questions from tree.
treeObj={
"Root_Element": {
"id": "myTree",
"dt": {
"choice": {
"id": '0',
"title": "Which color",
"description": "Choose color ?",
"choice": [
{
"id": 1,
"title": "Yellow",
"description": "Yellow ? ,
"choice": [
{
"id": 5,
"title": "Dark Yellow",
"description": "Dark Yellow ?
},
{
"id": 4,
"title": "Light Yellow",
"description": "Light Yellow ?
}
]
},
{
"id": 2,
"title": "Red",
"description": "Red ?"
},
{
"id": 3,
"title": "Green",
"description": "Green ?
}
]
}
}
}
}
If the number of levels in the JSON object is fixed and if it does not grow dynamically, you can use the ES6 destructuring to read the data from the nested JSON. Below is an example
var metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localization_tags: [ ],
last_edit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung"
}
],
url: "/en-US/docs/Tools/Scratchpad"
};
var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"

Categories

Resources