14

I'm starting with react and I have an array of objects called arrayToFilter I want to apply multiple filters to, ideally when a user changes the filter select options all filters should be applied to the array and the results returned into a filteredResults array to show, I already have each filter function and their constants but I don't know how to apply all the filters one after another...

Here is my code:

    const [ arrayToFilter, setArrayToFilter ] = useState([ /*array of objects to apply filters to*/ ]);
    const [ filteredResults, setFilteredResults] = useState([ /*array of filtered objects*/ ]);
    const [ categoryOptions, setCategoryOptions ] = useState([ 1,2,3 ]);
    const [ selectedCategoryOptions, setSelectedCategoryOptions ] = useState([ ]);
    const [ searchName, setSearchName ] = useState('');
    const [ hideVenuesWithoutDiscounts, setHideVenuesWithoutDiscounts ] = useState(true);

    const filterHideVenuesWithoutDiscounts = () => 
    {
        if(hideVenuesWithoutDiscounts)
        {
            return arrayToFilter.filter(item => item.discounts.length > 0);
        }
        else
        {
            return arrayToFilter;
        }   
    };

    const filterSelectedCategoryOptions = () => 
    {
        return arrayToFilter.filter(item => item.category_id.includes(selectedCategoryOptions));
    };

    const filtersearchName = () => 
    {
        return arrayToFilter.filter(item =>  item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1);
    };

    useEffect(() => 
    {
        //Filter options updated so apply all filters here
    },
    [searchName, hideVenuesWithoutDiscounts, selectedCategoryOptions]);

Notice useEffect, afaik this is like watch property in vue, I'd like to fire all the filters when a filter input changes

3 Answers 3

16

It's simple, you can pass the filtered array as a parameter to the filtering functions so that they can filter the filtered array.

For example:

const filterHideVenuesWithoutDiscounts = (array) => {
    if (hideVenuesWithoutDiscounts) {
        return array.filter((item) => item.discounts.length > 0);
    } else {
        return array;
    }
};

const filterSelectedCategoryOptions = (array) => {
    return array.filter((item) => item.category_id.includes(selectedCategoryOptions));
};

const filtersearchName = (array) => {
    return array.filter((item) => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1);
};

useEffect(() => {
    //Filter options updated so apply all filters here
    let result = arrayToFilter;
    result = filterHideVenuesWithoutDiscounts(result);
    result = filterSelectedCategoryOptions(result);
    result = filtersearchName(result);
    setArrayToFilter(result);
}, [searchName, hideVenuesWithoutDiscounts, selectedCategoryOptions]);
1
  • 3
    This was the easiest and really understandable answer even for someone like me who is very naive to JavaScript and React. Thanks!
    – Chipsy
    Commented Oct 29, 2021 at 8:37
7

You could chain multiple filters together in the array.

arrayToFilter
.filter(item => item.category_id.includes(selectedCategoryOptions))
.filter(item => item.discounts.length > 0)
.filter((item) => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1)
0
4

May I suggest one more approach, using Array.reduce, as chaining Array.filters will go over whole array every time that way which can be resource expensive if arrays are huge.

Example:

const filterMethods = [
  (item => item.category_id.includes(selectedCategoryOptions)),
  (item => item.discounts.length > 0),
  ((item) => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1)
]

const filteredArray = arrayToFilter.reduce((accumulator, currentItem) => {
  for (let i = 0; i < filterMethods.length; i++) {
    if (!filterMethods[i](currentItem)) {
      return accumulator
    }
  }
  return [...accumulator, currentItem]
}, [])

EDIT: After sleeping a while, I noticed that you can actually use Array.filter in similar way as reduce, without going multiple times over the array.

const filterMethods = [
  (item => item.category_id.includes(selectedCategoryOptions)),
  (item => item.discounts.length > 0),
  ((item) => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) != -1)
]

const filteredArray = arrayToFilter.filter((item) => {
  for (let i = 0; i < filterMethods.length; i++) {
    if (!filterMethods[i](item)) {
      return false
    }
  }
  return true
})
1
  • I've noticed that you could actually probably use Array.filter in similar way, look the edit, I think that would work too in your situation.
    – Mr Shumar
    Commented Sep 25, 2020 at 5:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.