curv
curv

Reputation: 3844

React JS recursive tree

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

Answers (1)

Felix Kling
Felix Kling

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

Related Questions