skip to Main Content

I have modal in a React app that renders a list of items with a list of nested values and a search bar. When the user types into the search bar, I need to be able to match that input to an item in the list and show the exact path to that match.

For example, my data looks like:

const dataList = [
  {
    label: 'Foo',
    value: 'foo',
    subItems: [
      {
        label: 'Sub bar 1',
        value: 'sub_bar_1',
        subItems: [
          { label: 'Nested baz 1', value: 'nested_baz_1' },
          { label: 'Nested baz 2', value: 'nested_baz_2' },
        ],
      },
      {
        label: 'Sub bar 2',
        value: 'sub_bar_2',
      },
    ],
  },
  {
    label: 'Bar',
    value: 'bar',
    subItems: [],
  },
];

And given the query string, Nested baz 2, I’d expect the output to be:

const filteredList = [{
   label: 'Foo',
   value: 'foo',
   subItems: [
     {
       label: 'Sub bar 1',
       value: 'sub_bar_1',
       subItems: [
         { label: 'Nested baz 2', value: 'nested_baz_2' },
       ],
     },
  ],
}];

I’ve tried multiple iterations of a recursive function and old-school for loops but nothing can give me the exact path to the object that matches my query string.

Is there anything I can change in this current implementation to get the desired output?

 const filterItems = (
   options: Option[],
   query: string
 ): Option[] => {
   return options.filter((option) => {
     if (option.label.toLowerCase().includes(query.toLowerCase())) {
       return true;
     }
     if (option.subItems) {
       return filterItems(option.subItems, query).length > 0;
     }
     return false;
   });
 };

 const filteredItems = useMemo(() => {
   return filterItems(options, query);
 }, [options, query]);
[EDIT]: update so that recursive call was calling the right function

2

Answers


  1. You can achieve this with the below

    const filterDataList = (dataList, searchTerm) => {
    return dataList
    .map(item => {
      if (item.label.includes(searchTerm)) {
        return item;
      }
    
      if (item.subItems && item.subItems.length > 0) {
        const filteredSubItems = filterDataList(item.subItems, searchTerm);
    
        if (filteredSubItems.length > 0) {
          return { ...item, subItems: filteredSubItems };
        }
      }
    
      return null;
    })
    .filter(item => item !== null);
    }
     const searchTerm = 'Nested baz 2';
     const filteredList = filterDataList(dataList, searchTerm);
    
    Login or Signup to reply.
  2. The problem with your implementation is that you lose the filtering you have applied here:

    return filterCategories(option.subItems, query).length > 0;
    

    The filtered array is not retained: only the length serves to return false/true, but when it is true, you still have all the subItems. You need to create new objects when the subItems property is going to get a reduced array.

    You could combine map with filter, where you map either a new object with reduced subItems, or undefined. Chain a filter call to only retain the objects:

    const filterItems = (
               options: Option[],
               query: string
            ): Option[] => {
        return options.map((option: Option) => {
            if (option.label.toLowerCase().includes(query.toLowerCase())) {
                return option;
            }
            if (option.subItems) {
                const subItems = filterItems(option.subItems, query);
                if (subItems.length > 0) return { ...option, subItems };
            }
        }).filter(Boolean);
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search