1
\$\begingroup\$

Today after watching a getting started course about React, I tried to code my very first React component, a "treeview" as the title says. It does work, but as a newbie I know there are many improvement that can be done.

It receives a data array like the one below:

[{
    descp: 'UNE',
    url: '/tree/une',
    icon: 'icon-list',
    children: [
        {
            descp: 'ATI',
            url: '/tree/une/ati',
            icon: 'icon-layers',
            children: [{
                    descp: 'ATI-VC',
                    url: '/tree/une/ati/ativc',
                    icon: 'icon-pin',
                    children: []
                },
                {
                    descp: 'ATI-CMG',
                    url: '/tree/une/ati/aticmg',
                    icon: 'icon-pin',
                    children: []
                }
            ]
        },
        {
            descp: 'EMGEF',
            url: '/tree/une/emgef',
            icon: 'icon-layers',
            children: []
        },
        {
            descp: 'ECIE',
            url: '/tree/une/ecie',
            icon: 'icon-layers',
            children: []
        },
        {
            descp: 'GEYSEL',
            url: '/tree/une/geysel',
            icon: 'icon-layers',
            children: []
        }
    ]
}]

Maybe the data format can be improve as well but the real deal is the component:

const MyTree = (props) => {
    const { list } = props;
    return (
        list.map((child,i)=>
            <MyTreeNestedItems key={i} data={child} />
        )
    );
};


const MyTreeNestedItems = (props) => {
    const { data } = props;

    const createChildren = (list) => {
        return (
            list.map((child,i)=>
                <MyTreeNestedItems key={i} data={child} />
            )
        )
    }

    let children = null;
    if (data.children.length) {
        children = createChildren(data.children);
    }

    return (
        <li className="nav-item">
            <a className="nav-link" href="#">
                <span className={data.icon}></span> {" "}
                { data.descp }
            </a>
            <ul style={{listStyleType:"none"}}>{children}</ul>
        </li>
    );
};

render(<MyTree list={tree} />,document.querySelector('.js-mytree'));

Any tips would be gratefully appreciated.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

You might notice that createChildren is almost the same as MyTree -- the only difference is that createChildren takes its argument directly instead of as a props object. So with a small change we can remove createChildren entirely:

const MyTreeNestedItems = (props) => {
    const { data } = props;

    let children = null;
    if (data.children.length) {
      children = <MyTree list={data.children} />;
    }

    return (
        <li className="nav-item">
            <a className="nav-link" href="#">
                <span className={data.icon}></span> {" "}
                { data.descp }
            </a>
            <ul style={{listStyleType:"none"}}>{children}</ul>
        </li>
    );
}

This can be a bit more compact as so:

const MyTreeNestedItems = ({ data }) => {
    return (
        <li className="nav-item">
            <a className="nav-link" href="#">
                <span className={data.icon}></span> {" "}
                { data.descp }
            </a>
            <ul style={{listStyleType:"none"}}>
                {data.children.length ? <MyTree list={data.children} /> : null}
            </ul>
        </li>
    );
};

Currently this code renders invalid HTML -- it renders with a <li> as the top level element but <li> should only be contained in a <ul>, <ol>, or <menu>.

Looking at the code so far, <MyTree> is rendered in two places -- it may be tempting to just add another <ul> around <MyTree list={tree} /> in the render() call. But then we would have two occurrences of a structure like this: <ul><MyTree/></ul>. It'd be easier to just move the <ul> into the <MyTree/>.

With that, the code now looks like this:

const MyTree = ({ list }) => {
    return (
        <ul style={{listStyleType:"none"}}>
            {list.map((child,i)=> <MyTreeNestedItems key={i} data={child} />)}
        </ul>
    );
};

const MyTreeNestedItems = ({ data }) => {
    return (
        <li className="nav-item">
            <a className="nav-link" href="#">
                <span className={data.icon}></span> {" "}
                { data.descp }
            </a>
            {data.children.length ? <MyTree list={data.children} /> : null}
        </li>
    );
};

render(<MyTree list={tree} />, document.querySelector('.js-mytree'));

From here it's a matter of taste. Both of the components are just directly returning a value, so we can use short arrow function syntax now: (...) => (value) instead of (...) => { return (value); }

Also, the components are now simple enough that I would just combine the two.

All in, that leaves us with this final version of the code:

const MyTree = ({ list }) => (
    <ul style={{listStyleType:"none"}}>
        {list.map(({icon, descp, children}, i) => (
            <li className="nav-item" key={i}>
                <a className="nav-link" href="#">
                    <span className={icon} /> {descp}
                </a>
                {children.length ? <MyTree list={children} /> : null}
            </li>
        ))}
    </ul>
);

render(<MyTree list={tree} />, document.querySelector('.js-mytree'));
\$\endgroup\$
1
  • \$\begingroup\$ Thanks a lot George, that's a great improvement of my code and explanation. Now in a few days I will be focusing on implementing collapse/expand functionality to the component. \$\endgroup\$
    – Nestor
    Aug 21, 2019 at 11:21

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.