Closed vwkd closed 1 year ago
As a temporary workaround, I'm using the following function to filter Fuse's search results and return new filtered item
properties.
It depends on the utilities deepFilter
and deepMerge
.
It also depends on a fuse_options
object with includeMatches: true
and keys
with an array of string keys.
function filterResults(results) {
let resultsNew = [];
for (const result of results) {
const { item, matches } = result;
let resultNew = {};
// note: group multiple matches per key, otherwise would become separate entries in highest ancestor array if processes separately
for (const key of fuse_options.keys) {
const matchesForKey = matches.filter(({ key: k }) => k == key);
if (matchesForKey.length) {
const valuesForKey = matchesForKey.map(({ value }) => value);
const resultForKey = deepFilter(item, key.split("."), obj => valuesForKey.some(value => obj === value));
resultNew = deepMerge(resultNew, resultForKey);
}
}
resultsNew.push(resultNew);
}
return resultsNew;
}
It's certainly not ideal but it seems to get the job done.
Also you might need to adapt it slightly depending on your specific results, like if you only want to filter the matches for certain keys.
This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 30 days
hey @vwkd but by using filter
, doesn't it lose all power of fuse.js? such as text proximity?
Have you figured out how to solve this using fuse?
Thanks
anything here?
Our use case would really benefit from this! We're also having to filter ourselves to get this working. @krisk, any chance we can reopen this issue?
In case it's helpful to anyone in the interim, here's our solution to filter results of the following shape:
type NestedOption<T> = {
data: T;
children: T[];
}
const exampleData = [{
data: {
value: "fruit",
label: "Fruit",
},
children: [
{
value: "raspberry",
label: "Black Raspberry",
},
{
value: "orange",
label: "Blood Orange",
},
],
}]
The filterOptionArrayForSearch
param is just a small wrapper around Fuse that searches for the query
text via the label
key.
const flattenedDataForSearch = options.flatMap(({ data, children }) => [
data,
...children,
]);
const flatMatches = new Set(
filterOptionArrayForSearch({
options,
query,
})
);
/**
* If a parent matches the search query, we want to show all of its children. Otherwise,
* filter the children down to only those that match the search query.
*/
const parentsWithFilteredChildren = options.map(({ data, children }) => ({
data,
children: flatMatches.has(data)
? children
: children.filter((child) => flatMatches.has(child)),
}));
/**
* Filter the options to those that have matching parent data or at least one matching child.
*/
const result = parentsWithFilteredChildren.filter(({ data, children }) => {
return flatMatches.has(data) || children.length > 0;
});
Description
Fuse is conveniently capable of nested search into objects with arrays as properties.
Given the key
a.b
, searching forlorem
yields the first element of the root array.However, when listing the results of a search it’s sometimes desirable to filter out any elements of any subarrays that do not contain any matches.
Fuse currently can not filter out non-matching elements of subarrays. Also the option
includeMatches
only gives the matched leaf values instead of the elements of the root array.See also inactive https://github.com/krisk/Fuse/issues/186 which gained some support.
Describe the solution you'd like
An option
includeNonMatchingArrayElements: false
that allows to filter out non-matching subarray elements.Describe alternatives you've considered
Filtering the results manually by comparing them to the matches. However, the general solution for arbitrarily deeply nested objects is non-trivial, StackOverflow only has a non-general solution. Also it duplicates a lot of search-related code in the app which shouldn’t be necessary.