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