简体   繁体   中英

Filtering array data with multiple conditions with ES6

I'm using an es6 filter function that takes in both search string and value from state hook that should allow the user to search data by a string, and then apply a filter property a11yType as well. I can filter the data by a11yType alone, or by search string alone, but unable to get both to work in tandem. Not sure what's going on here:

Search string hook (value taken from text input)

const [searchFilter, setFilter] = useState('');

Accessibility string value (value taken from button options)

const [a11yType, setA11yType] = useState('');

Data filtered and mapped

data &&
    data
        .filter(
            (f) =>
                f[0].includes(searchFilter.toUpperCase()) ||
                (searchFilter === '' && f[1].type === a11yType),
        )
        .map((item, i) => {
            {
                item[1].type &&
                    rows.push(
                        createData(
                            `${item[0]}`,
                            `${item[1].type}`,
                            `${[...new Set(item[1].urls)].length}`,
                            `${item[1].urls}`,
                        ),
                    );
            }
        });

You need to be very specific with your boolean logic. The computer cannot guess what you mean (unlike talking to humans).

You want:

  1. If user is searching but no type selected match the search
  2. If user select type but not searching match the type
  3. If user is both searching and selecting type match both

Therefore your filter function should be:

(f) =>
    (a11yType === '' && f[0].includes(searchFilter.toUpperCase())) ||
    (searchFilter === '' && f[1].type === a11yType) ||
    (f[0].includes(searchFilter.toUpperCase()) && f[1].type === a11yType)

In the short term this should fix your issue.

However, note that this will become quickly unmanageable the more conditions you add. Say you add another option: "category", your code will grow very big:

(f) =>
    (category === '' && a11yType === '' && f[0].includes(searchFilter.toUpperCase())) ||
    (category === '' && searchFilter === '' && f[1].type === a11yType) ||
    (category === '' && f[0].includes(searchFilter.toUpperCase()) && f[1].type === a11yType) ||
    (a11yType === '' && searchFilter === '' && f[1].category === category) ||
    (searchFilter === '' && f[1].type === a11yType &&  f[1].category === category) ||
    (f[0].includes(searchFilter.toUpperCase()) && f[1].type === a11yType  &&  f[1].category === category)

Not to mention it becomes very unreadable. I'm not sure just looking at the function above if the function is well-formed or if I have a syntax error - much less if the logic is correct!

For this kind of logic, one thing to note is that if the condition is empty then we ignore the condition. For the .filter() function, ignoring a condition means we always return true .

Refactoring the code we can do the following:

(f) => {
    const searchCond = searchFilter === '' || f[0].includes(searchFilter.toUpperCase()));

    const typeCond = a11yType === '' || f[1].type === a11yType;

    return searchCond && typeCond;
}

Basically we switch the logic from OR of AND (sum of products) to AND or OR (product of sums). This allows us to avoid typing the conditions twice. Notice that in the refactored code above we check f[1].type === a11yType only once instead of needing to retype it for the third case of matching both the search and the type.

In this way adding another option requires you to add only one more condition:

(f) => {
    const searchCond = searchFilter === '' || f[0].includes(searchFilter.toUpperCase()));

    const typeCond = a11yType === '' || f[1].type === a11yType;

    const categoryCond = category === '' || f[1].category === category;

    return searchCond && typeCond && categoryCond;
}

Of course, you can also write this in the compact form you seem to like:

(f) => 
    (searchFilter === '' || f[0].includes(searchFilter.toUpperCase()))) &&
    (a11yType === '' || f[1].type === a11yType) &&;
    (category === '' || f[1].category === category);

I'm not sure I fully understand the goal you want to achieve, but I think you've made a logical error.
First, you used the or operator in your statement. It checks whether at least one of the conditions is true. So you first check if the f[0] includes the searchString and if it doesn't it proceeds to the second statement.
Second, you check if the searchFilter is empty and if it does, you check if the f[1] type is equal to a11yType.
And this works just like you described it. You can only filter by the search phrase or by the a11yType if the search phrase is empty.
So if you want to combine two filters (check what results include a searchString and have a matching a11yType), just try this:

f[0].includes(searchFilter.toUpperCase())
&& // only if the search string is contained in f[0]
(a11yType === '' || f[1].type === a11yType) // a11yType is empty or the result satisfies the filter

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM