1

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
        }]
    }]
}
4
  • 1
    It depends on what output you want. The fact that you are adding the React elements to arrays makes things complicated. Ideally the function simply returns an element. Commented Feb 9, 2017 at 17:10
  • 1
    @FelixKling totally agree with u.. he can take advantage also of the concept of props as well as compo lifecycle Commented Feb 9, 2017 at 17:12
  • The reason for the array is because i wanted nested toggleable divs prob a better way- will get this working first then refactor Commented Feb 9, 2017 at 17:28
  • @FelixKling - how would I pass a custom object to the child components. The idea being, on each child I want the path within the json it came from e.g. Level 1 - {value: "Fruit"} Level 2 - {value: "Fruit", nested_values: [{ value: 'Tropical'}] } Level 3 - {value: "Fruit", nested_values: [{ value: 'Tropical', nested_values:[{ value: 'Pineapple' }]}] } I somehow need to get to the point where I select a tree element and the entire structure (json) of that is sent as an action to a Redux store Commented Feb 13, 2017 at 14:40

1 Answer 1

3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

I just missed the return!?
Well, the use of this.tmpArr and this.tmpArrB was also questionable.
Ah yes i see what you have done! Thanks Felix will try this out when i get home
Worked well thanks Felix - I refactored it heavily and now think it looks much better, updated my answer. Any input would be most appreciated!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.