2

Hi everone i have a issue or more over i want to know "How can i create a tree table kind off structure using the nested json". With out using any 3rd part lib/package

I have a nested json like below :

[
  {
    "id": 1,
    "name": "VehicleData",
    "owner": "admin",
    "sub_details": [
      {
        "sub_name": "Lexus",
        "sub_id": 4,
      },
      {
        "sub_name": "BMW",
        "sub_id": 3,
      }
    ]
  },
  {
    "id": 2,
    "name": "Mobiles,
    "owner": "admin",
    "sub_details": [
      {
        "sub_name": "Apple",
        "sub_id": 2,
      }
    ]
  },
  {
    "id": 3,
    "name": "Laptop",
    "owner": "admin",
    "sub_details": []
  }
]

What i have tried :-

import React, {useState, useEffect} from 'react';
import classes from './TableData.module.css';

const TableData = () => {
    const [oldData, newData] = useState([]);

    useEffect(() => {
        fetchData();
    }, []);

    const Id = (dataId) => {
        alert(dataId);
    };
    const Name = (dataName) => {
        alert(dataName);
    };

    const fetchData = async () => {
        try {
            const response = await fetch('someapi/json');
            const data = await response.json();
            newData(data);
        } catch (e) {
            console.warn(e)
        }
    }


    const DisplayData = oldData.map((data) => {
        return (
            <tr key={
                data.id
            }>
                <td> {
                    data.id
                } </td>
                <td> {
                    data.name
                } </td>
                <td> {
                    data.owner
                } </td>
                <td>
                    <button onClick={
                        () => Id(data.id)
                    }>getId</button>
                    <button onClick={
                        () => Name(data.name)
                    }>getname</button>
                </td>
            </tr>
        )
    })


    return (
        <div>
            <div>
                <table>
                    <thead>
                        <tr>
                            <th>id</th>
                            <th>name</th>
                            <th>owner</th>
                        </tr>
                    </thead>
                    <tbody> {DisplayData} </tbody>
                </table>

            </div>


        </div>
    )

}

export default TableData;

what i have done : I have created a table using json and also added button for getting the parent data

what i need and how can i do :

  1. How can i add create the nested structure without using 3rd party plugins and also add button to each row .

  2. How can i get the parent data and child data on each child row button click .

  3. How can i able to toggle & keep open up the nested structure .

if any info url are there it will be great full ?

Madpop
  • 729
  • 4
  • 24
  • 61

2 Answers2

1

In React, you must break your data into components. Working with the data as is, in multi-level nesting, is considered bad design - see here for a detailed explanation.

With this in mind, I dedicated one component for each nesting level. This way, the row can manage its sub-row hidden state. Here's the final code - see full demo here - click the "toggle" button:

index.js:

import React, { useState, useEffect } from "react";
import "./styles.css"

const Table = () => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      const response = await fetch("./data.json");
      const data = await response.json();
      setItems(data);
    } catch (e) {
      console.warn(e);
    }
  };
  
  return <div>
    {
      items.map((data) => { 
        return <TableData key={data.id} data={data} />
      })
    }
  </div>;
};

const TableData = (props) => {
  const data = props.data;
  const [subHidden, setSubHidden] = useState(false);
  
  const Id = (dataId) => {
    alert(dataId);
  };
  const Name = (dataName) => {
    alert(dataName);
  };
  const Toggle = () => {
    setSubHidden(state => { 
      return !state;
    });
  };

  return (
    <div >
      <span> {data.id} </span>
      <span> {data.name} </span>
      <span> {data.owner} </span>
      <span>
        <button onClick={() => Id(data.id)}>getId</button>
        <button onClick={() => Name(data.name)}>getname</button>
        <button onClick={() => Toggle()}>toggle</button>
      </span>
      <TableSubDetails 
        subDetails={data.sub_details}
        style={{ display: subHidden ? 'none' : 'block'}}
      />
    </div>
  );
}

const TableSubDetails = (props) => {
  const subDetails = props.subDetails;
  return (
    <div className="subData" style={props.style}>
      {
        subDetails.map((subData) => { 
          return <div key={subData.sub_id}>
            <span> {subData.sub_id} </span>
            <span> {subData.sub_name} </span>
          </div>
        })
      }
    </div>
  );
}

export default Table;

styles.css:

.subData {
  padding: 5px 15px;
}  
OfirD
  • 9,442
  • 5
  • 47
  • 90
1

Here is my version of the solution by updating your existing code with some logic

const TableData = () => {
  const [oldData, newData] = useState([]);
  const [id, setId] = useState({});

  useEffect(() => {
    fetchData();
  }, []);

  const Id = (dataId) => {
    alert(dataId);
  };
  const Name = (dataName) => {
    alert(dataName);
  };

  const fetchData = async () => {
    try {
      newData(jsonData);
    } catch (e) {
      console.warn(e);
    }
  };

  const updateId = useCallback((dataId) => {
    setId((prevState) => {
      let updatedState = { ...prevState };
      const hasId = updatedState[dataId] ? true : false;

      if (hasId) {
        delete updatedState[dataId];
      } else {
        updatedState = { ...updatedState, [dataId]: dataId };
      }

      return {
        ...updatedState
      };
    });
  }, []);

  const renderTableRows = useCallback(
    (details, isChild = false) => {
      let renderData = [];

      details.forEach((detail) => {
        const detailId = isChild ? detail["sub_id"] : detail["id"];
        const name = isChild ? detail["sub_name"] : detail["name"];

        const childData = (
          <tr key={`${name}-${detailId}`}>
            <td> {detailId} </td>
            <td> {name} </td>
            <td> {detail.owner} </td>
            <td>
              <button onClick={() => Id(detailId)}>getId</button>
              <button onClick={() => Name(name)}>getname</button>
              {detail.sub_details?.length > 0 && (
                <button
                  onClick={() => {
                    updateId(detailId);
                  }}
                >
                  {id[detailId] === detailId ? "Hide" : "Show"} child
                </button>
              )}
            </td>
          </tr>
        );

        renderData.push(childData);

        if (id[detailId] === detailId && detail.sub_details?.length > 0) {
          const childData = renderTableRows(detail.sub_details, true);
          renderData = [...renderData, ...childData];
        }
      });

      return renderData;
    },
    [id, updateId]
  );

  const DisplayData = useMemo(() => {
    return renderTableRows(oldData);
  }, [oldData, renderTableRows]);

  return (
    <div>
      <div>
        <table>
          <thead>
            <tr>
              <th>id</th>
              <th>name</th>
              <th>owner</th>
            </tr>
          </thead>
          <tbody> {DisplayData} </tbody>
        </table>
      </div>
    </div>
  );
};

export default TableData;

here is the example link.

Thanks