Skip to content

Filter array based on multiple filters

I have a table that I want to be able to apply multiple filters to. My current code will filter by email OR status but I want it to do both so a user can refine the results.

I’ve tried adding && instead of || in the filteredInvoices variable.

I allow the user to select the filters they want to use and store this in an object in useState. I also keep track of the status if the filter is active or inactive.

const [filter, setFilter] = useState({active: false})

A filter object where a user has selected their filters would look like this:

filter: {active: true, customerEmail: '[email protected]', status: 'open'}

When a user applies this filter to the table I call a function called updateFilter() in useEffect when the filter is changed. This will then set the filtered array to state when is then iterated over in the return.

const updateFilter = () => {
    const filteredInvoices = invoices.filter((inv) => {
        const emailMatch = filter.customerEmail && inv.customerEmail.toLowerCase().includes(filter.customerEmail.toLowerCase())
        const statusMatch = inv.status === filter.status
        return emailMatch || statusMatch
    })
    setListToRender(filteredInvoices)
}
useEffect(() => {
    if (filter.active) {
        updateFilter()
    }
}, [filter])

The status that is being filtered is an array of objects.

How am I able to filter by multiple condiitions that are added into the filter object?

Is there a common design for this that would allow me to add additional filters to the filter object and it also work?

For example – [email protected] has 10 invoices – 3 open, 4 paid and 3 void. If the filter object looks like this:

How can I filter to show only invoices for that customer that are open.

Thanks in advance for any advice!

Answer

Since you want to be able to not just match specific key-value pairs, but may also need to do stuff like pre-processing of the value before filtering (like you do with converting email to lower case), then a generic approach might work best for you. You can have something like this

const updateFilter = () => {
    const filteredInvoices = invoices.filter(invoice => {
    let validInvoice = true;
    for (const [key, value] of Object.entries(filter)) {
      if (Array.isArray(value)) {
        if (!value.includes(invoice[key])) {
          validInvoice = false;
        }
      }
      else if (typeof value === 'function') {
        if (!value(invoice[key])) {
          validInvoice = false;
        }
      }
      else {
        if (value !== invoice[key]) validInvoice = false;
      }
    }
    return validInvoice;
  });
  setListToRender(filteredInvoices)
}

With this approach, you can have a filter that has either raw values, functions or arrays. Like so:

const filter = {
    // This will check if the invoice 'active' property is true
    active: true,
    // This will check that the status is NOT equal to pending
    status: (status) => status !== 'pending',
    // This will check if the email matches ANY of these values
    email: ['[email protected]', '[email protected]']
};

This approach allows more nuance than the simple key/value matching approach, and can let you customise your filters more deeply