Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove children objects recursively from object?

I am working on solution

I have created basic tree kind of table whenever user click on expand data related to clicked row will appear under it based on row data

I have achieved basic functionality of expand/collapse upto N nested levels.

But i am stuck with only one problem, so basically all row have conditional expand button based on array having multiple values

Lets say it is split array having 3 entries county,city,state

Default loaded data will be fetched from api, now i have to check array that is there any split available! if yes than i make expand button visible

Consider this scenario

const split = ["country","city","state"]

this is Ui will look like

+ Data_1
+ Data_2

on click of button + new data table row will be rendered based on next split available in our case it is country so visual representation will be like

- Data_1
   Country_1
   Country_2
+ Data_2

Here country does not have expand button as user have not added next split yet, lets add city, and assume user have clicked Country_1 so data will be like

    - Data_1
       - Country_1
           City_1
           City_2
       + Country_2
    + Data_2

My solution works fine till this level now lets say user have removed country from split that all nodes of country and city should be removed and - icon of data_1 should be changed to +

Here is my code

import React, {useState, useEffect, useRef, Fragment} from "react";
  import _ from "lodash";
  import axios from "axios";

  class TableRowData extends React.Component {
    state = {
      showIcon: false,
      selection: [],
      data: [],
      splitOption: ["campid"]
    };
    constructor(props) {
      super(props);
    }

    componentDidMount() {
      const checkIfSplitExistOnMount = (currentSplit) => {
        const i = _.findIndex(this.state.splitOption, function(el) {
          return el === currentSplit;
        });

        if (this.state.splitOption[i + 1]) {
          return this.state.splitOption[i + 1];
        } else {
            return null;
        }
      }
      const getReportData = () => {
        axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then((res) => {
          const rowData = res.data.map((row) => {
            row.name = this.state.splitOption[0];
            row.isExpanded = false;
            row.currentSplit = this.state.splitOption[0];
            row.nextSplit = checkIfSplitExistOnMount(this.state.splitOption[0])
            row.parentId = 0;
            row.isVisble = true;
            //console.log(row)
            return row;
          });
          this.setState({
            data: rowData
          }, () => { //console.log(this.state.data)
          });
        });
      }
      getReportData()
    }

    render() {
      // update state function
      const updateState = () => {
        this.setState({
          data: [...this.state.data],
          splitOption: [...this.state.splitOption],
          selection: [...this.state.selection],
        }, () => {})
      }

      // recusively update parent and child
      const recursion = (obj) => {
         let row = obj;
         row.isExpanded = row.isExpanded;
         row.currentSplit = row.currentSplit;
         row.nextSplit = checkIfSplitExist(row.currentSplit)

         if (row.children && row.children.length > 0) { // check if has children
            row.children.forEach(v => { // if has children do the same recursion for every children
              recursion(v);
            });
         }
         return row; // return final new object
       }

       const recursionDel = (obj,split) => {
           var row = obj;
           row.currentSplit = row.currentSplit;
           row.nextSplit = checkIfSplitExist(row.currentSplit)
           if (row.children && row.children.length > 0) { // check if has children
             row.children.forEach(v => { // if has children do the same recursion for every children
               recursionDel(v);
             });
          }
          return row; // return final new object
        }

      // function to check if next split is there or not if there than return nextsplit
      const checkIfSplitExist = (currentSplit) => {
        const i = _.findIndex(this.state.splitOption, function(el) {
          return el === currentSplit;
        });
        if(i !== -1) {
          if (this.state.splitOption[i + 1]) {
            return this.state.splitOption[i + 1];
           } else {
            return null;
          }
        }

      }

      // recursive update whenever split added
      const recursiveUpdate = (data) => {
        const prevData = [...data];
        return prevData.map((row) => {
          const updatedData =  recursion(row);
          return row;
        });
      }

      // function to delete child and parent node recursively
      const recursiveDelete = (data,split) => {
        const prevData = [...data];
        return prevData.map((row) => {
          const data =  recursionDel(row,split);
          return row;
        });
      }

      const addNewSplit = (split) => {
        const i = _.findIndex(this.state.splitOption, function(el) {
          return el === split;
        });
        if(i === -1) {
            this.setState(
              {
                splitOption:[...this.state.splitOption,split]
              },
              ()=>{
                var rowData = recursiveUpdate(this.state.data)
                this.setState({data:rowData})
              }
          );
        } else {
          const prevData = [...this.state.splitOption];
          var index = prevData.indexOf(split);
          prevData.splice(index,1)
          if(index!==-1) {
            this.setState(
                {
                    splitOption:prevData
                },
                ()=> {
                  var rowData = recursiveDelete(this.state.data,split)
                  this.setState({data:rowData})
                }
              )
          }
        }

      }

      // add lazyload expand data
      const ExpandableTableRow = ({rows}) => {

        const expandRow = (row) => {
          row.children = [
            {
              id: "_" + Math.random().toString(36).substr(2, 5),
              name: row.id + "_" + row.nextSplit,
              isExpanded: false,
              parentId: row.id,
              currentSplit: row.nextSplit,
              nextSplit: checkIfSplitExist(row.nextSplit),
              isVisble:true
            }, {
              id: "_" + Math.random().toString(36).substr(2, 5),
              name: row.id + "_" + row.nextSplit,
              isExpanded: false,
              parentId: row.id,
              currentSplit: row.nextSplit,
              nextSplit: checkIfSplitExist(row.nextSplit),
              isVisble:true
            }
          ];
          row.isExpanded = true;
          updateState();
        };

        // call whenever - click
        const collapseRow = (row) => {
          delete row.children;
          row.isExpanded = false;
          updateState();
        };

        // toggle
        const ExpandCollapsToggle = ({row, expandRow, collapseRow}) => {
          // display +/- only if nextsplit is not undefined or null
          if (row.nextSplit) {
            if (row.isExpanded === true) {
              return (<button type="button" onClick={() => collapseRow(row)}>
                -
              </button>);
            } else {
              return (<button type="button" onClick={() => expandRow(row)}>
                +
              </button>);
            }
          } else {
            return null;
          }
        };



        if (rows) {
          return rows.map((row) => {
          //  if(!_.isEmpty(row)) {
              return (<Fragment key={row.id}>
                <tr key={row.id}>
                  <td>
                    <ExpandCollapsToggle row={row} expandRow={expandRow} collapseRow={collapseRow}/>{" "}
                    {row.split}
                    - {row.id}
                  </td>
                  <td>{row.name}</td>
                </tr>
                <ExpandableTableRow rows={row.children}/>
              </Fragment>);
          //  }

          });
        } else {
          return null;
        }
      };

      const splitData = this.state.splitOption.map((ob) => {
        return (<Fragment key={ob}><span>{ob}</span> > </Fragment>)
      })

      if (this.state.data) {
        return (
          <Fragment>
            {splitData} <br/>
            <button onClick = {()=>addNewSplit("name")}>camp name</button>
            <button onClick = {()=>addNewSplit("os")}>os</button>
            <button onClick = {()=>addNewSplit("country")}>country</button>
          <ExpandableTableRow rows={this.state.data} />
        </Fragment>
        );
      } else {
        return null;
      }
    }
  }

  export default TableRowData;

Also i have create example of codesandbox.io - Link

Here is how you play with ui to replicate scenario

  • First click on camp name, expand icon will appear
  • Now expand if you want to, you can see data according split under
  • Now add one more split OS or Country and you can see expand icon with 2nd level rows
  • Next step is to remove "Camp Name", Here is issue when camp name is removed, table should be re render according available splits, in our case user's all row should be removed and + icon must be there are we have next split os or country available, i used default split id, it will be there always
like image 894
Janak Prajapati Avatar asked Nov 21 '25 19:11

Janak Prajapati


1 Answers

import React, { useState, useEffect, useRef, Fragment } from "react";
import axios from "axios";

const test_data = [{
  "id":1,
    "name":"Leanne Graham",
    "username":"Bret",
    "email":"[email protected]",
    "address":{
      "street":"Kulas Light",
      "suite":"Apt. 556",
      "city":"Gwenborough",
      "zipcode":"92998-3874",
      "geo":{
        "lat":"-37.3159",
        "lng":"81.1496"
      }
    },
    "phone":"1-770-736-8031 x56442",
    "website":"hildegard.org",
    "company":{
      "name":"Romaguera-Crona",
      "catchPhrase":"Multi-layered client-server neural-net",
      "bs":"harness real-time e-markets"
    }
}];

class TableRowData extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      showIcon: false,
      selection: [],
      data: [],
      splitOption: ["campid"]
    };
  }

  // function to check if next split is there or not if there than return nextsplit
  checkIfSplitExist = (currentSplit) => {
    const i = this.state.splitOption.indexOf(currentSplit);

    if (i > -1 && this.state.splitOption[i + 1]) {
      return this.state.splitOption[i + 1];
    }
    return null;
  }

  getReportData = () => {
    // axios.get("https://jsonplaceholder.typicode.com/users?_start=0&_limit=1").then(({data}) => {
      this.setState({
        data: test_data.map((row) => {
        row.name = this.state.splitOption[0];
        row.isExpanded = false;
        row.currentSplit = this.state.splitOption[0];
        row.nextSplit = this.checkIfSplitExist(this.state.splitOption[0])
        row.parentId = 0;
        row.isVisble = true;
        console.log(row)
        return row;
      })
      });
    // });
  }
  
  componentDidMount() {
    this.getReportData()
  }

  render() {
    // update state function
    const updateState = () => {
      this.setState({
        data: [...this.state.data],
        splitOption: [...this.state.splitOption],
        selection: [...this.state.selection],
      }, () => { })
    }

    const recursionUpdateAndDeleteRow = (parentRow, childRow, split, index = 0) => {
      childRow.children && childRow.children.forEach((r) => {
        recursionUpdateAndDeleteRow(childRow, r, split, index + 1);
      });

      if (parentRow && split.indexOf(childRow.currentSplit) == -1) {
        delete parentRow.children;
      }

      childRow.currentSplit = split[index];
      childRow.nextSplit = split[index + 1] || null;
      if (!childRow.children) {
        childRow.isExpanded = false;
      }
    }

    const recursionUpdateAndDeleteRows = (rows, split) => {
      const _copy = [...rows];
      _copy.forEach((row) => {
        recursionUpdateAndDeleteRow(null, row, split);
      });
      return _copy;
    }

    const toggleSplit = (split) => {
      const index = this.state.splitOption.indexOf(split);
      let currentSplitOptions = [...this.state.splitOption];
      if (index > -1) {
        currentSplitOptions.splice(index, 1)
      }
      else {
        currentSplitOptions.push(split);
      }

      const _data = recursionUpdateAndDeleteRows(this.state.data, currentSplitOptions);

      this.setState({
        splitOption: currentSplitOptions,
        data: _data
      })
    }

    // add lazyload expand data
    const ExpandableTableRow = ({ rows }) => {

      const expandRow = (row) => {
        row.children = [
          {
            id: "_" + Math.random().toString(36).substr(2, 5),
            name: row.id + "_" + row.nextSplit,
            isExpanded: false,
            parentId: row.id,
            currentSplit: row.nextSplit,
            nextSplit: this.checkIfSplitExist(row.nextSplit),
            isVisble: true
          }, {
            id: "_" + Math.random().toString(36).substr(2, 5),
            name: row.id + "_" + row.nextSplit,
            isExpanded: false,
            parentId: row.id,
            currentSplit: row.nextSplit,
            nextSplit: this.checkIfSplitExist(row.nextSplit),
            isVisble: true
          }
        ];
        row.isExpanded = true;
        updateState();
      };

      // call whenever - click
      const collapseRow = (row) => {
        delete row.children;
        row.isExpanded = false;
        updateState();
      };

      // toggle
      const ExpandCollapsToggle = ({ row }) => {
        // display +/- only if nextsplit is not undefined or null
        if (row.nextSplit) {
          if (row.isExpanded) {
            return (
              <button type="button" onClick={() => collapseRow(row)}>
                -
              </button>
            );
          }
          return (
            <button type="button" onClick={() => expandRow(row)}>
              +
            </button>
          );
        }
        return null;
      };

      if (rows) {
        return rows.map((row) => {
          return (
            <Fragment key={row.id}>
            <tr key={row.id}>
              <td>
                <ExpandCollapsToggle
                  row={row}
                />
                {" "}{row.split} - {row.id}
              </td>
              <td>{row.name}</td>
            </tr>
            <ExpandableTableRow rows={row.children} />
          </Fragment>
          );
        });
      } else {
        return null;
      }
    };

    if (this.state.data) {
      return (
        <Fragment>
          {this.state.splitOption.join(', ')} <br />
          <button onClick={() => toggleSplit("name")}>
            camp name
          </button>
          <button onClick={() => toggleSplit("os")}>os</button>
          <button onClick={() => toggleSplit("country")}>country</button>
          <br />
          <ExpandableTableRow rows={this.state.data} />
        </Fragment>
      );
    } else {
      return null;
    }
  }
}

export default function App() {
  return (
    <div>
      <TableRowData />
    </div>
  );
}

Here working example

like image 51
Chandan Avatar answered Nov 24 '25 09:11

Chandan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!