kentcdodds / match-sorter

Simple, expected, and deterministic best-match sorting of an array in JavaScript
https://npm.im/match-sorter
MIT License
3.73k stars 129 forks source link

Tip: Search multiple terms (AND filter) #97

Closed SweVictor closed 4 years ago

SweVictor commented 4 years ago

Hi,

Great library, simple and easy to use. Using it in conjunction with react-table, specifically to do a global filter (one input box) to filter across multiple columns in the table.

For me the behavior when searching for multiple terms (space-separated words) does not work as I as the user would expect. However, it works great when match-sorter:ing for each search term separately. So, I thought I'd share this as a recipe for anyone looking for the same thing.

Basic implementation for AND-searching for multiple terms. Each term will be fuzzy-matched, gradually reducing the resulting list. First search term is prioritized (its match will sort the results). Written in typescript, just remove the typings to get javascript.

export function fuzzyGlobalTextFilter<T extends object>(
    rows: Array<T>,
    keys: Array<string>,
    filterValue: string,
) {
    const terms = filterValue?.split(" ");
    if (!terms || !(typeof terms === "object")) {
        return rows;
    }

    return terms.reduceRight((results, term) => matchSorter(results, term, {keys}), rows);
}

For the specific use-case for react-table if someone is interested I landed in this. Basic difference is difference of data-structure, strategy is the same.

rows is the dataset (array, where each element contains the actual data inside the values field). ids is an array of strings containing the keys to search (so it will search row.values.keyName) filterValue is the space-separated string of search terms (typically the user input)

export function fuzzyGlobalTextFilterForReactTable<T extends object>(
    rows: Array<Row<T>>,
    ids: Array<IdType<T>>,
    filterValue: FilterValue,
) {
    const terms = (filterValue as string)?.split(" ");
    if (!terms || !(typeof terms === "object")) {
        return rows;
    }

    const filterFunction = (data: Array<Row<T>>, filterVal: string) =>
        matchSorter(data, filterVal, {
            keys: ids.map((id) => (row) => row.values[id]),
        });

    return terms.reduceRight((results, term) => filterFunction(results, term), rows);
}

Hope this helps someone!

/Victor

SweVictor commented 4 years ago

As a comment for the above approach, what I am missing is the ability to prioritize columns. Say you have name and tags in a data row object. If all else fails a "contains" match in tags is better than returning nothing, but "contains" in name would be a much better match.

Would it be possible to adjust the rank per key? So key = 'name', rank= +5, key='tags, rank = -10 for example? Not sure if values make sense, but basically to offset the ranking to say that "EQUAL match for tags should give the same rank as CONTAINS match for name" for example.

Either way, great lib!

/Victor

kentcdodds commented 4 years ago

Awesome! Thank you for taking the time to share Victor! Want to make an example in codesandbox and share a link in the docs?

SweVictor commented 4 years ago

@kentcdodds: Sure, why not. You have after all taken the time to create the library!

An example is here https://codesandbox.io/s/match-sorter-example-forked-1ko35

"Share a link in the docs" - does that mean create a PR? (never mind, figured it out. Github is helpful these days 😄 )