Skip to content
Advertisement

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: 'johndoe@gmail.com', 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 – johndoe@gmail.com 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!

Advertisement

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: ['john@doe.com', 'cho@mumma.com']
};

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

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement