Reputation: 3844
I am trying to render a tree structure recursively using ReactJS. I did a vanilla example pretty quickly (below). Now I'm porting this to React and I've been looking at it for a while and can not figure out how to do it. I have included my React example under the vanilla one.
Vanilla JS
createTree(data, isSub, lev) {
let level = lev || 0;
let html = (isSub) ? '<div class="filter-body">' : ''; // Wrap with div if true
if (isSub) { level++; }
for (let i = 0, len = data.length; i < len; i++) {
if (typeof(data[i].nested_values) === 'object') {
html += '<div class="filter-group level-' + level + '">';
if (isSub) {
html += '<div class="filter-heading">' + data[i].value + '</div>';
} else { // Submenu found, but top level list item.
html += '<div class="filter-heading">' + data[i].value + '</div>';
}
// Submenu found, call function recursively
html += this.createTree(data[i].nested_values, true, level);
html += '</div>';
} else {
html += '<span>' + data[i].value + '</span>' // No submenu, end of tree
}
}
html += (isSub) ? '</div>' : '';
return html;
}
ReactJS version
I'm adding to an array then mapping over it to output in my render method. The part I'm struggling to wrap my head around is how to inject the recursive call.. I feel I'm very close but been looking at it for too long, any ideas?
createTree(data, isSub, lev) {
let level = lev || 0;
for (let i in data) {
if (typeof(data[i].nested_values) === 'object') { // Sub array found, build structure
this.tmpArrB.push(
<div class={"filter-group level-" + (level)}>
<div class="filter-heading">{data[i].value}</div>
{this.createTree(data[i].nested_values, true, level)} // How to do this properly?
</div>
);
} else { // No submenu, bottom of tree
this.tmpArrB.push(
<span key={i}>
{data[i].value}
</span>);
}
}
this.tmpArr.push(<div className='filter-body open'>{this.tmpArrB}</div>);
}
Updated version (working)
createSelectionHierarchy(data, isSub, level = 1) {
let children = [];
if (isSub) { level++; }
for (let i = 0, len = data.length; i < len; i++) {
if (typeof(data[i].nested_values) === 'object') { // Sub array found, build structure
children.push(
<FilterItem key={i} data={data[i]} level={level}>
{this.createSelectionHierarchy(data[i].nested_values, true, level)}
</FilterItem>
);
} else { // No submenu, bottom of tree
children.push(
<span key={i}>
{data[i].value}
</span>);
}
}
return children;
}
Dummy JSON
{
"possible_values": [{
"value": "Fruit",
"occurrence_count": 5,
"nested_values": [{
"value": "Berries",
"occurrence_count": 3,
"nested_values": [{
"value": "Strawberry",
"occurrence_count": 1
}, {
"value": "Blackberry",
"occurrence_count": 1
}, {
"value": "Raspberry",
"occurrence_count": 1
}, {
"value": "Redcurrant",
"occurrence_count": 1
}, {
"value": "Blackcurrant",
"occurrence_count": 1
}, {
"value": "Gooseberry",
"occurrence_count": 1
}, {
"value": "Cranberry",
"occurrence_count": 1
}, {
"value": "Whitecurrant",
"occurrence_count": 1
}, {
"value": "Loganberry",
"occurrence_count": 1
}, {
"value": "Strawberry",
"occurrence_count": 1
}]
}, {
"value": "Tropical",
"occurrence_count": 2,
"nested_values": [{
"value": "Pineapple",
"occurrence_count": 1
}, {
"value": "Mango",
"occurrence_count": 1
}, {
"value": "Guava",
"occurrence_count": 1
}, {
"value": "Passion Fruit",
"occurrence_count": 1
}, {
"value": "Dragon Fruit",
"occurrence_count": 1
}]
}]
}, {
"value": "Vegetable",
"occurrence_count": 2,
"nested_values": [{
"value": "Potato",
"occurrence_count": 3
}, {
"value": "Leek",
"occurrence_count": 3
}, {
"value": "Onion",
"occurrence_count": 3
}, {
"value": "Sprout",
"occurrence_count": 3
}, {
"value": "Carrot",
"occurrence_count": 3
}, {
"value": "Runner Bean",
"occurrence_count": 3
}, {
"value": "Swede",
"occurrence_count": 3
}, {
"value": "Turnip",
"occurrence_count": 3
}, {
"value": "Parsnip",
"occurrence_count": 3
}, {
"value": "Kale",
"occurrence_count": 3
}, {
"value": "Spinach",
"occurrence_count": 3
}, {
"value": "Artichoke",
"occurrence_count": 3
}, {
"value": "Broad Bean",
"occurrence_count": 3
}, {
"value": "French Bean",
"occurrence_count": 3
}, {
"value": "Brocolli",
"occurrence_count": 3
}, {
"value": "Cauliflower",
"occurrence_count": 3
}, {
"value": "White Cabbage",
"occurrence_count": 3
}, {
"value": "Red Cabbage",
"occurrence_count": 3
}, {
"value": "Savoy Cabbage",
"occurrence_count": 3
}, {
"value": "Corn",
"occurrence_count": 3
}, {
"value": "Courgette",
"occurrence_count": 3
}, {
"value": "Mange Tout",
"occurrence_count": 3
}, {
"value": "Sweet Potato",
"occurrence_count": 3
}, {
"value": "Pak Choi",
"occurrence_count": 3
}]
}]
}
Upvotes: 1
Views: 5151
Reputation: 816422
This is not different from any other recursive function. To make this work, the function has to return something though.
This line
{this.createTree(data[i].nested_values, true, level)} // How to do this properly?
means to pass the return value of this.createTree
as child to an element. Since your function doesn't return anything, that doesn't work.
Corrected example:
createTree(data, isSub, lev) {
let level = lev || 0;
let children = [];
for (let i in data) {
if (typeof(data[i].nested_values) === 'object') { // Sub array found, build structure
children.push(
<div class={"filter-group level-" + (level)}>
<div class="filter-heading">{data[i].value}</div>
{this.createTree(data[i].nested_values, true, level)}
</div>
);
} else { // No submenu, bottom of tree
children.push(
<span key={i}>
{data[i].value}
</span>
);
}
}
return <div className='filter-body open'>{children}</div>;
}
If data
is an array, consider using Array#map
instead.
Upvotes: 3