Home > front end >  Filter array of object with sub objects
Filter array of object with sub objects

Time:02-27

Apologies if there is already an answer for this, I've been unable to locate one that has worked for me.

I have defined a menu system for React where the information is stored in an array of objects. Each object has the posibility of having sub items (objects in another array) and I'm looking to filter the entire menu for values.

Menu Object

[
  {
    "key": "Dashboard",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "title": "Dashboard",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/",
        "children": "Dashboard"
      },
      "_owner": null,
      "_store": {}
    }
  },
  {
    "key": "Clients",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Clients",
    "submenus": [
      {
        "key": "Clients.Add",
        "title": "Create Client",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Clients/create",
            "children": "Create Client"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Clients.List",
        "title": "List Clients",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Clients/",
            "children": "List Clients"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Clients.Search",
        "title": "Search Clients",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Clients/search",
            "children": "Search Clients"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "key": "Contractors",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Contractors",
    "submenus": [
      {
        "key": "Contractors.Add",
        "title": "Create Contractor",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Contractors/create",
            "children": "Create Contractor"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Contractors.List",
        "title": "List Contractors",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Contractors/",
            "children": "List Contractors"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Contractors.Search",
        "title": "Search Contractors",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Contractors/search",
            "children": "Search Contractors"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "key": "Jobs",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Jobs",
    "submenus": [
      {
        "key": "Jobs.Add",
        "title": "Create Job",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Jobs/create",
            "children": "Create Job"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Jobs.List",
        "title": "List Jobs",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Jobs/",
            "children": "List Jobs"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Jobs.Search",
        "title": "Search Jobs",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Jobs/search",
            "children": "Search Jobs"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "isDivider": true
  },
  {
    "key": "Config",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Configuration",
    "submenus": [
      {
        "key": "Config.Category.Add",
        "title": "Create Category",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Category/create",
            "children": "Create Category"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Category.List",
        "title": "List Categories",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Category/",
            "children": "List Categories"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Specialist.Add",
        "title": "Create Specialist",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Specialist/create",
            "children": "Create Specialist"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Specialist.List",
        "title": "List Specialists",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Specialist/",
            "children": "List Specialists"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Status.Add",
        "title": "Create Status",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Status/create",
            "children": "Create Status"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Status.List",
        "title": "List Statuses",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Status/",
            "children": "List Statuses"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "key": "UserManagement",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "User Management",
    "submenus": [
      {
        "key": "UserManagement.Add",
        "title": "Create User",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/UserManagement/create",
            "children": "Create User"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "UserManagement.List",
        "title": "List Users",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/UserManagement/",
            "children": "List Users"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "isDivider": true
  },
  {
    "key": "LockScreen",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "title": "Lock Screen",
    "component": "Lock Screen"
  }
]

So if I were to filter this on title for the value "list" it should return back

[
  
  {
    "key": "Clients",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Clients",
    "submenus": [
      {
        "key": "Clients.List",
        "title": "List Clients",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Clients/",
            "children": "List Clients"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "key": "Contractors",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Contractors",
    "submenus": [      
      {
        "key": "Contractors.List",
        "title": "List Contractors",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Contractors/",
            "children": "List Contractors"
          },
          "_owner": null,
          "_store": {}
        }
      },
    ]
  },
  {
    "key": "Jobs",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Jobs",
    "submenus": [
      {
        "key": "Jobs.List",
        "title": "List Jobs",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Jobs/",
            "children": "List Jobs"
          },
          "_owner": null,
          "_store": {}
        }
      },
      
    ]
  },  
  {
    "key": "Config",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "Configuration",
    "submenus": [
      {
        "key": "Config.Category.List",
        "title": "List Categories",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Category/",
            "children": "List Categories"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Specialist.List",
        "title": "List Specialists",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Specialist/",
            "children": "List Specialists"
          },
          "_owner": null,
          "_store": {}
        }
      },
      {
        "key": "Config.Status.List",
        "title": "List Statuses",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/Config/Status/",
            "children": "List Statuses"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  },
  {
    "key": "UserManagement",
    "icon": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {},
      "_owner": null,
      "_store": {}
    },
    "isSubMenu": true,
    "title": "User Management",
    "submenus": [
      {
        "key": "UserManagement.List",
        "title": "List Users",
        "component": {
          "type": {},
          "key": null,
          "ref": null,
          "props": {
            "to": "/admin/UserManagement/",
            "children": "List Users"
          },
          "_owner": null,
          "_store": {}
        }
      }
    ]
  }
]

Or it should return back

[
  

  {
    "key": "Clients.List",
    "title": "List Clients",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Clients/",
        "children": "List Clients"
      },
      "_owner": null,
      "_store": {}
    }
  },
    
  {
    "key": "Contractors.List",
    "title": "List Contractors",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Contractors/",
        "children": "List Contractors"
      },
      "_owner": null,
      "_store": {}
    }
  },

  {
    "key": "Jobs.List",
    "title": "List Jobs",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Jobs/",
        "children": "List Jobs"
      },
      "_owner": null,
      "_store": {}
    }
  },
  
  {
    "key": "Config.Category.List",
    "title": "List Categories",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Config/Category/",
        "children": "List Categories"
      },
      "_owner": null,
      "_store": {}
    }
  },
  {
    "key": "Config.Specialist.List",
    "title": "List Specialists",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Config/Specialist/",
        "children": "List Specialists"
      },
      "_owner": null,
      "_store": {}
    }
  },
  {
    "key": "Config.Status.List",
    "title": "List Statuses",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/Config/Status/",
        "children": "List Statuses"
      },
      "_owner": null,
      "_store": {}
    }
  }, 
  {
    "key": "UserManagement.List",
    "title": "List Users",
    "component": {
      "type": {},
      "key": null,
      "ref": null,
      "props": {
        "to": "/admin/UserManagement/",
        "children": "List Users"
      },
      "_owner": null,
      "_store": {}
    }
  }
]

At present I have the following which works for the 1st level objects, but the sub menu's all get returned:

const MenuSearch = (value) => {
        const SearchTerm = value.toLowerCase();
        console.log(SearchTerm, MenuItems);
        if (!value || value.length < 1){
            // show all menu items
            SetMenuItemsFiltered(MenuItems);   
        } else {
            let temp = [...MenuItems];

            SetMenuItemsFiltered(temp.filter((MenuItem) => {
                if (!MenuItem.isSubMenu && !MenuItem.isDivider){
                    return MenuItem.title.toLowerCase().includes(SearchTerm);
                } else if (MenuItem.isSubMenu){
                    return MenuItem.submenus.filter((SubMenuItem) => {
                        const SubMenuItemLabel = SubMenuItem.title.toLowerCase();
                        console.log(SubMenuItemLabel, SearchTerm, SubMenuItemLabel.includes(SearchTerm))
                        return SubMenuItemLabel.includes(SearchTerm.toLowerCase());
                    })
                }
            }));
        }
    }

CodePudding user response:

The issue is that you are filtering the MenuItem.submenus but not storing the values filtered. Instead, you are just returning the whole MenuItem.submenu

So It should be

MenuItem.submenus= MenuItem.submenus.filter((SubMenuItem) => { 
  const SubMenuItemLabel = SubMenuItem.title.toLowerCase(); 
  return SubMenuItemLabel.includes(SearchTerm.toLowerCase());
 })

and return true if MenuItem.submenus.length>0.

So final code would be

 temp.filter((MenuItem) => {
   if (!MenuItem.isSubMenu && !MenuItem.isDivider){
       return MenuItem.title.toLowerCase().includes(SearchTerm);
   } else if (MenuItem.isSubMenu){
       MenuItem.submenus= MenuItem.submenus.filter((SubMenuItem) => {
         const SubMenuItemLabel = SubMenuItem.title.toLowerCase();
         console.log(SubMenuItemLabel, SearchTerm, 
         SubMenuItemLabel.includes(SearchTerm))
         return SubMenuItemLabel.includes(SearchTerm.toLowerCase());
        })
     return MenuItem.submenus.length;
   }
})

CodePudding user response:

I ended up taking a different approach with this, rather than returning on the filter I would use the filter to create a new array of objects that matched. I've posted the answer here but will continue to check if anyone comes up with a better solution.

const MenuSearch = (value) => {
        const SearchTerm = value.toLowerCase();
        if (!value || value.length < 1){
            // show all menu items
            SetMenuItemsFiltered(MenuItems);   
        } else {
            let FilteredItems = [];

            // filter the data
            MenuItems.filter((MenuItem) => {
                if (!MenuItem.isSubMenu && !MenuItem.isDivider){
                    if (MenuItem.title.toLowerCase().includes(SearchTerm)){
                        FilteredItems.push(MenuItem);
                    }
                    //return MenuItem.title.toLowerCase().includes(SearchTerm);
                } else if (MenuItem.isSubMenu){
                    MenuItem.submenus.filter((SubMenuItem) => {
                        const SubMenuItemLabel = SubMenuItem.title.toLowerCase();
                        // return SubMenuItemLabel.includes(SearchTerm);
                        if (SubMenuItemLabel.includes(SearchTerm)){
                            FilteredItems.push(SubMenuItem);
                        }
                    })
                }
            });

            SetMenuItemsFiltered(FilteredItems);
        }
    }

CodePudding user response:

The ~ in !~value.length is just my preferred way to say "don't start from -1 and consider it 0"

const MenuSearch = (value) => {
  const searchWord = value.toLowerCase();

  if (!value || !~value.length) {
    // early return.
    return SetMenuItemsFiltered(MenuItems);
  }

  const result = data.reduce((prev, curr) => {
    const title = curr.title?.toLowerCase();

    if (!curr.submenus && !curr.isDivider) {
      if (title.includes(searchWord)) {
        return prev.concat(curr);
      }
    }

    if (curr.isSubMenu) {
      return prev.concat(
        curr.submenus.filter((item) =>
          item.title.toLowerCase().includes(searchWord)
        ),
      );
    }

    return prev;
  }, []);

  SetMenuItemsFiltered(result);
};

I think this is what you're looking for:

const objectsFilter = require("objectsfilter");
const data = require("./data.json");

const MenuSearch = (value) => {
  const searchWord = value.toLowerCase();

  if (!value || !~value.length) {
    // early return.
    return SetMenuItemsFiltered(MenuItems);
  }

  const result = data.reduce((prev, curr) => {
    const title = curr.title?.toLowerCase();

    if (!curr.submenus && !curr.isDivider) {
      if (title.includes(searchWord)) {
        return prev.concat(curr);
      }
    }

    if (curr.isSubMenu) {
      const submenus = [];

      objectsFilter.objectsForEach(curr, (currentItem, key) => {
        if (key === "submenus") {
          submenus.push(
            currentItem.filter((item) =>
              item.title.toLowerCase().includes(searchWord)
            ),
          );
        }
      });

      return prev.concat({
        ...curr,
        submenus,
      });
    }

    return prev;
  }, []);


  SetMenuItemsFiltered(result);
};

Objectsfilter: https://github.com/Meno-101/objectsfilter

npm: https://www.npmjs.com/package/objectsfilter

  • Related