react-instantsearch
react-instantsearch copied to clipboard
useMenu does not work properly when filtering for one level of an otherwise hierarchical facet
🐛 Bug description
When using useMenu
to filter by a single level of an otherwise hierarchical attribute, no results are shown in the UI, despite being in the response.
After using transformItems
to achieve the desired result (as in the sandbox), refine
does not work either. Sending refine seems to apply tags properly, but it does not update filters or results.
I have no issue with useRefinementList
locally, but I want to achieve a single select component. However, when trying to replicate refinement list in the sandbox, it does not work, and behaves similarly to useMenu.
🔍 Bug reproduction
Steps to reproduce the behavior:
- Go to
[this sandbox](https://codesandbox.io/s/youthful-tesla-99xsuz?file=/components/MenuUsingHierarchical.tsx)
- Results as expected when using a custom transformItems, that takes this data from _rawResults
- Comment out transformItems
- There will be no results 🐛
- Notice that useEffect which happens on mount does not apply filtering at all, despite creating a tag 🐛
- Replace
attribute
inuseMenu
with a simple attribute, such as "categories" and things will work as expected
Live reproduction:
https://codesandbox.io/s/youthful-tesla-99xsuz?file=/components/MenuUsingHierarchical.tsx
💭 Expected behavior
Using any single attribute in the form of hierarchicalAttribute.level2
in useMenu
behaves as any other attribute, no matter whether it's also a hierarchical facet or not.
Environment
- React InstantSearch Hooks version: [e.g. 6.29.0]
- React version: [17.0.2]
- Browser: [Chrome]
- OS: [Windows / Ubuntu with WSL]
"instantsearch.js": "4.43.0",
"algoliasearch": "4.13.1",
"react-instantsearch-hooks-web": "6.29.0",
"@algolia/client-search": "4.13.1",
"@algolia/autocomplete-core": "1.7.1",
"@algolia/autocomplete-plugin-query-suggestions": "1.7.1",
"@algolia/autocomplete-preset-algolia": "1.7.1",
"react": "17.0.2",
"react-dom": "17.0.2",
Could you explain further what you exactly want to achieve? Is there also a hierarchical menu in the UI, or is there only a hierarchical attribute used in the useMenu?
I don't fully understand yet what your use case is, but could you use useHierarchicalMenu
to achieve it?
What I am trying to achieve is just a normal usage of "useMenu". One single filter:
The use case is that we have multiple ways of navigating the hierarchy. On one of the views, let's say we are on level 2 already, we want to use <Configure filters="filter.level2 = Level 1 > Level 2"/>
to pre-apply a filter that the user cannot change. Then we want to display level 3 filter but only level 3. We know there will not be a level 4. In that case, the level 3 filter should show all possible children of the level 2 pre-applied configuration.
This should not matter that it is hierarchical, right? However, I found that Algolia seems to mess up with this particular attribute, and I have confirmed that the common factor is that all these attributes are structured the same way hierarchical attributes are. So hierarchy: {level1: "value", level2: "value > value2"}
.
So InstantSearch must have some "smart" logic for detecting whether a filter is hierarchical, and then treating it wrongly in useMenu
.
I only briefly looked at the code for this, but this is the line in InstantSearch.js that sounds like it could cause the issue I am having: connectMenu.ts. This was just looking for "hierarchical" in the useMenu connector, so take this with a grain of salt🙂
I tried using useHierarchicalMenu
to solve this, however, due to the bug with rootPath
, using that is very inconvenient, since rooth path does not work for setting a starting level: https://github.com/algolia/react-instantsearch/issues/3540. I have found a workaround for this, by calling refine
to the level I want on mount, but I am not satisfied with that as I have gotten inconsistent results with that.
I think I have the same problem.
useMenu
basically can't handle hierarchal attributes like this breadcrumb_hierarchy: {lvl0: "a", lvl1: "a > b", lvl2: "a > b > c"}
when the property attribute
is defined like breadcrumb_hierarchy.lvl1
or breadcrumb_hierarchy.lvl2
. breadcrumb_hierarchy.lvl0
works.
@Haroenv Cany idea when this bug gets priority? @bartosz-urban-danfoss Were you able to find a workaround?
@joker-777, I don't understand what experience exactly you're trying to achieve, do you have a code sample, diagram or something else?
@Haroenv the purpose is to use a filter that is in the format of hierarchy.level2
as a single select filter of ONLY THAT LEVEL. The last part is important.
I have linked to Algolia code in previous comments where I think it goes wrong : https://github.com/algolia/instantsearch.js/blob/b1805c6740dff77f1e1f9c448c27909775d525e2/src/connectors/menu/connectMenu.ts#L348
@joker-777 I was able to achieve a workaround by using useRefinementList
, which does not have this issue, and extra logic to turn it into a single select. I used useClearRefinements
to do so. I also use transformItems
to only take the last level in the hierarchy string for the filter value label. e.g. Level 1 > Level 2
-> Level 2
/**
* This hook is a workaround around an issue I reported here: https://github.com/algolia/react-instantsearch/issues/3543
*
* - useMenu does not work for single level filters that are in a typical hierarchical format
* - useRefinementList seems to work fine, but it's multi select, so we need to have some logic to make it into single select
* - other workaround are a lot more messy
* - cannot use useHierarchicalMenu due to another issue where we cannot start from a higher level: https://github.com/algolia/react-instantsearch/issues/3540
*/
export const useAlgoliaFixedFilter = ({
id,
onFilterSelect,
}: UseAlgoliaFixedFilterProps): UseAlgoliaFixedFilterResponseProps => {
const { refine, items, canRefine } = useRefinementList({
attribute: id,
limit: ALGOLIA_FILTER_LIMIT,
transformItems: transformLevelFilter,
});
const { refine: clearRefine } = useClearRefinements({ includedAttributes: [id] });
const onChangeWithClear = useMemo(
() =>
handleFixedOnChangeWithClear(
id,
refine,
canRefine,
clearRefine,
onFilterSelect as (type: string, optionValue: string) => void,
),
[id, refine, canRefine, clearRefine, onFilterSelect],
);
const onChange = useMemo(
() => fixedOnChange(refine, canRefine, clearRefine),
[refine, canRefine, clearRefine],
);
return {
filterValues: items,
onChange,
onChangeWithClear,
};
};
export const handleFixedOnChangeWithClear = (
id: string,
refine: (value: string) => void,
canRefine: boolean,
clearRefine: () => void,
onFilterSelect: (type: string, optionValue: string) => void,
): OnSingleChangeWithClearType => {
return (e: CustomEvent<SelectChangeEventArgsType<false>>) => {
const { value: selectValue, meta } = e.detail;
if (meta.action === 'clear') {
clearRefine();
} else if (canRefine && selectValue?.value) {
const refinement = String(selectValue.value);
// Make multi select (refinementList) into single by clearing previous refinements
clearRefine();
refine(refinement);
onFilterSelect?.(id, refinement);
}
};
};
export const fixedOnChange = (
refine: (value: string) => void,
canRefine: boolean,
clearRefine: () => void,
): OnChangeType => {
return (selectedValue: string) => {
if (canRefine && selectedValue) {
// Make multi select (refinementList) into single by clearing previous refinements
clearRefine();
refine(selectedValue);
}
};
};
@bartosz-urban-danfoss Thanks a lot. I will try it out as well.
We're doing a round of cleanup before migrating this repository to the new InstantSearch monorepo. This issue seems not to have generated much activity lately, so we're going to close it.
Having a >
separator in the facet values is not currently supported with the <Menu>
widget, instead you can use a <RefinementList>
widget like so: https://codesandbox.io/s/trusting-kapitsa-ywxxrf?file=/src/App.tsx:1110-1168