javascriptreactjsarraystreetreeview

JS filter on array tree


I want to filter on my array tree and display only founded node based on the label

this is my output with my code : enter image description here

I've added a input search and when I type into I update my state but I don't know how to update my tree to display only filtered items.. I'm using Material UI and there is no filter option on documentation I've tried flat data but don'k know how to adapt it on my example.. this is my code :

import * as React from 'react';
import Box from '@mui/material/Box';
import { RichTreeView } from '@mui/x-tree-view/RichTreeView';
import { TreeViewBaseItem } from '@mui/x-tree-view/models';

const MUI_X_PRODUCTS: TreeViewBaseItem[] = [
  {
    id: 'grid',
    label: 'Data Grid',
    children: [
      { id: 'grid-community', label: '@mui/x-data-grid' },
      { id: 'grid-pro', label: '@mui/x-data-grid-pro' },
      { id: 'grid-premium', label: '@mui/x-data-grid-premium' },
    ],
  },
  {
    id: 'pickers',
    label: 'Date and Time Pickers',
    children: [
      { id: 'pickers-community', label: '@mui/x-date-pickers' },
      { id: 'pickers-pro', label: '@mui/x-date-pickers-pro' },
    ],
  },
  {
    id: 'charts',
    label: 'Charts',
    children: [{ id: 'charts-community', label: '@mui/x-charts' }],
  },
  {
    id: 'tree-view',
    label: 'Tree View',
    children: [{ id: 'tree-view-community', label: '@mui/x-tree-view' }],
  },
  {
    id: '1',
    label: 'test',
    children: [
      { id: '11', label: 'AAAA' },
      { id: '12', label: 'BBBB' },
    ],
  },
];

function getParentIds(items: TreeViewBaseItem[], id: string) {
  const parentIds: string[] = [];

  for (const item of items) {
    if (item.children) {
      if (item.children.some((child) => child.id === id)) {
        // The current item is a parent of the supplied id
        parentIds.push(item.id);

        // Recursively call the function for the parent item
        const grandParentIds = getParentIds(items, item.id);
        parentIds.push(...grandParentIds);
      } else {
        // Recursively call the function for the children of the current item
        const childParentIds = getParentIds(item.children, id);
        parentIds.push(...childParentIds);
      }
    }
  }

  return parentIds;
}

function getParentNode(
  items: TreeViewBaseItem[],
  id: string
): TreeViewBaseItem | undefined {
  for (const item of items) {
    if (item.children) {
      if (item.children.some((child) => child.id === id)) {
        // The current item is the parent of the supplied id
        return item;
      } else {
        // Recursively call the function for the children of the current item
        const parentNode = getParentNode(item.children, id);
        if (parentNode) {
          return parentNode;
        }
      }
    }
  }

  // No parent found
  return undefined;
}

function getSelectedIdsAndChildrenIds(
  items: TreeViewBaseItem[],
  selectedIds: string[]
) {
  const selectedIdIncludingChildrenIds = new Set([...selectedIds]);

  for (const item of items) {
    if (selectedIds.includes(item.id)) {
      // Add the current item's id to the result array
      selectedIdIncludingChildrenIds.add(item.id);

      // Recursively call the function for the children of the current item
      if (item.children) {
        const childrenIds = item.children.map((child) => child.id);
        const childrenSelectedIds = getSelectedIdsAndChildrenIds(
          item.children,
          childrenIds
        );
        childrenSelectedIds.forEach((selectedId) =>
          selectedIdIncludingChildrenIds.add(selectedId)
        );
      }
    } else if (item.children) {
      // walk the children to see if selections lay in there also
      const childrenSelectedIds = getSelectedIdsAndChildrenIds(
        item.children,
        selectedIds
      );
      childrenSelectedIds.forEach((selectedId) =>
        selectedIdIncludingChildrenIds.add(selectedId)
      );
    }
  }

  return [...Array.from(selectedIdIncludingChildrenIds)];
}

function determineIdsToSet(
  items: TreeViewBaseItem[],
  newIds: string[],
  currentIds: string[]
) {
  const isDeselectingNode = currentIds.length > newIds.length;
  if (isDeselectingNode) {
    const removed = currentIds.filter((id) => !newIds.includes(id))[0];
    const parentIdsToRemove = getParentIds(items, removed);
    const childIdsToRemove = getSelectedIdsAndChildrenIds(items, [removed]);

    const newIdsWithParentsAndChildrenRemoved = newIds.filter(
      (id) => !parentIdsToRemove.includes(id) && !childIdsToRemove.includes(id)
    );

    return newIdsWithParentsAndChildrenRemoved;
  }

  const added = newIds.filter((id) => !currentIds.includes(id))[0];
  const parent = getParentNode(items, added);
  if (parent) {
    const childIds = parent.children?.map((node) => node.id) ?? [];
    const allChildrenSelected = childIds.every((id) => newIds.includes(id));
    if (allChildrenSelected) {
      return [...getSelectedIdsAndChildrenIds(items, newIds), parent.id];
    }
  }
  return getSelectedIdsAndChildrenIds(items, newIds);
}

export default function CheckboxSelection() {
  const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
  const [data, setData] = React.useState(MUI_X_PRODUCTS);
  const [filterok, setFilterok] = React.useState<string[]>([]);
  const [text, setText] = React.useState<string>('');

  function myFlat(a: any, prefix = '') {
    return a.reduce(function (flattened: any, { id, label, children }) {
      id = prefix + id;

      return flattened
        .concat([{ id, label }])
        .concat(children ? myFlat(children, id) : []);
    }, []);
  }

  React.useEffect(() => {
    if (selectedIds.length > 0) {
      console.log(selectedIds);
    }
  }, [selectedIds]);
  React.useEffect(() => {
    if (text !== '') {
      console.log(text);
    }
  }, [text]);

  const filter = (e: any) => {
    setText(e.target.value);
  };

  const handleSelectedItemsChange = (
    _event: React.SyntheticEvent,
    ids: string[]
  ) => {
    setSelectedIds(determineIdsToSet(MUI_X_PRODUCTS, ids, selectedIds));
  };

  return (
    <Box sx={{ height: 264, flexGrow: 1, maxWidth: 400 }}>
      <input type="text" onChange={filter} value={text} />
      <RichTreeView
        multiSelect={true}
        checkboxSelection={true}
        selectedItems={selectedIds}
        onSelectedItemsChange={handleSelectedItemsChange}
        items={data}
      />
    </Box>
  );
}

the demo link is here : DEMO

flat data but don(t know how to adapt it to my example


Solution

  •   const filter = (e: any) => {
        setText(e.target.value);
        const result = MUI_X_PRODUCTS.filter((d) => {
          return d.label.includes(text); // condition as per your requirement
        });
        setData(result);
      };
    
    return d.label.includes(text) || d.children.some(child => child.label.includes(text))