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.

asked Aug 20, 2019 at 19:52
\$\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'));
answered Aug 21, 2019 at 7:34
\$\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\$ Commented Aug 21, 2019 at 11:21

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.