skip to Main Content

I am having a object that looks like the following:

export const data = {
  "Category 1": [
    {
      key: "1",
      label: "Applications",
      children: [
        {
          key: "3",
          label: "Browser",
          children: [
            {
              key: "4",
              label: "Mozilla",
              children: []
            },
            {
              key: "5",
              label: "Firefox",
              children: []
            }
          ]
        }
      ]
    }
  ],
  "Category 2": [
    {
      key: "2",
      label: "OS",
      children: [
        {
          key: "6",
          label: "Windows",
          children: []
        }
      ]
    }
  ]
};


This should basically draw a tree with two entries with Category 1 and Category 2 and its children should be drawn recursively.

Category 1
   ---- Applications
          --- Browser
                --- Mozilla
                --- Firefox
Category 2
   ---- OS
          --- Windows

This should be dynamically rendered. The number of first level objects will be determined using the number of keys of the object. I have tried with an array of objects and it works. But when I am having it in the above format, I am unable to render it as a tree. Help would be greatful.

Sandbox: https://codesandbox.io/s/react-hooks-counter-demo-forked-zsxsxh?file=/src/Tree.js

When I am trying it with treeData it works, but with data inside constants.js it doesnt work.

import React from "react";
import TreeNode from "./TreeNode";

const Tree = ({ data = [] }) => {
  return (
    <div className="d-tree">
      <ul className="d-flex d-tree-container flex-column">
        {data.map((tree) => (
          <TreeNode node={tree} />
        ))}
      </ul>
    </div>
  );
};

export default Tree;

I tried with Object.entries to render the tree, it doesnt work

3

Answers


  1. Your data structure is different from treeData, although the children are in the same format.

    Instead of an object at the top-level:

    export const data = {
      "Category 1": [ ... ],
      "Category 2": [ ... ],
    };
    

    you should have an array with key, label and children:

    export const data = [
      {
        key: "Category 1",
        label: "Category 1",
        children: [ ... ]
      }, ...
    ];
    

    If you really can’t edit the data (it’s right there …) you could map the top-level entries using the property name as key and label:

    const dataAsArray = Object.entries(data).map(([key, value]) =>
      ({ key, label: key, children: value })
    )
    

    Added in response to comment

    If that’s your preferred input format then you could put the data conversion into your Tree component:

    const Tree = ({ data = [] }) => {
      const trees = React.useMemo(() => (
        data instanceof Array
          ? data
          : Object.entries(data).map(
              ([key, children]) => ({ key, label: key, children })
          )
      ), [data]);
        
      return (
        <div className="d-tree">
          <ul className="d-flex d-tree-container flex-column">
            {trees.map((tree) => (
              <TreeNode node={tree} />
            ))}
          </ul>
        </div>
      );
    };
    
    Login or Signup to reply.
  2. In tree.js you can validate if data is not an array and transform it:

    Tree.js

    const Tree = ({ data = [] }) => {
      if (!(data instanceof Array)) {
        data = Object.keys(data).map((key) => {
          return { label: key, children: data[key] };
        });
      }
    
      return (
        <div className="d-tree">
          <ul className="d-flex d-tree-container flex-column">
            {data.map((tree) => (
              <TreeNode node={tree} />
            ))}
          </ul>
        </div>
      );
    };
    

    https://codesandbox.io/s/react-hooks-counter-demo-forked-3hxcgq

    Login or Signup to reply.
  3. you could write the Tree component like this and pass down the key (category) and the value (items) of Object.entries as props to TreeNode

    const Tree = ({ data = [] }) => {
      return (
        <div className="d-tree">
          <ul className="d-flex d-tree-container flex-column">
            {Object.entries(data).map(([category, items]) => (
              <TreeNode
                key={category}
                node={{ label: category, children: items }}
              />
            ))}
          </ul>
        </div>
      );
    };
    

    In the TreeNode you can recursively render TreeNode components based on if it has children or not. Here is a resource on that.

    Also you can add styles only to the first level.
    I used a prop called level and defaulted it to 1, then I give styles only if the level is 1 (used inline styles for simplicity). For the next levels, I increment the level prop by 1.

    const TreeNode = ({ node, level = 1 }) => {
      const [childVisible, setChildVisiblity] = useState(false);
      const hasChild = node.children && node.children.length > 0;
    
      const nodeStyle = level === 1 ? { fontWeight: "bold", color: "red" } : {};
    
      return (
        <li className="d-tree-node border-0">
          <div className="d-flex" onClick={(e) => setChildVisiblity((v) => !v)}>
            <div className="col d-tree-head" style={nodeStyle}>
              {node.label}
            </div>
          </div>
    
          {hasChild && childVisible && (
            <div className="d-tree-content">
              <ul className="d-flex d-tree-container flex-column">
                {node.children.map((item) => (
                  <TreeNode key={item.key} node={item} level={level + 1} />
                ))}
              </ul>
            </div>
          )}
        </li>
      );
    };
    
    const data = {  "Category 1": [    {      key: "1",      label: "Applications",      children: [        {          key: "3",          label: "Browser",          children: [            {              key: "4",              label: "Mozilla",              children: []            },            {              key: "5",              label: "Firefox",              children: []            }          ]        }      ]   }  ],  "Category 2": [    {      key: "2",      label: "OS",      children: [        {          key: "6",          label: "Windows",          children: []        }      ]    }  ]};
    
    const TreeNode = ({ node, level = 1 }) => {
      const [childVisible, setChildVisiblity] = React.useState(false);
      const hasChild = node.children && node.children.length > 0;
    
      const nodeStyle = level === 1 ? { fontWeight: "bold", color: "red" } : {};
    
      return (
        <li className="d-tree-node border-0">
          <div className="d-flex" onClick={(e) => setChildVisiblity((v) => !v)}>
            {hasChild && (
              <div
                className={`d-inline d-tree-toggler ${
                  childVisible ? "active" : ""
                }`}
              >
               {'>'}
              </div>
            )}
            <div className="col d-tree-head" style={nodeStyle}>
              {node.label}
            </div>
          </div>
    
          {hasChild && childVisible && (
            <div className="d-tree-content">
              <ul className="d-flex d-tree-container flex-column">
                {node.children.map((item) => (
                  <TreeNode key={item.key} node={item} level={level + 1} />
                ))}
              </ul>
            </div>
          )}
        </li>
      );
    };
    
    const Tree = ({ data = [] }) => {
      return (
        <div className="d-tree">
          <ul className="d-flex d-tree-container flex-column">
            {Object.entries(data).map(([category, items]) => (
              <TreeNode
                key={category}
                node={{ label: category, children: items }}
              />
            ))}
          </ul>
        </div>
      );
    };
    
    
    const TreeList = () => {
      return <Tree data={data} />;
    };
    
    ReactDOM.render(<TreeList />, document.querySelector('.app'));
    .d-tree-container {
      list-style: none;
      padding: 0;
    }
    
    .d-tree-node {
      padding: 0.75rem 1.25rem;
    }
    
    .d-tree-toggler.active {
      transform: rotate(45deg);
    }
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <div class='app'></div>

    Edit React Hooks Counter Demo (forked)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search