react-instantsearch icon indicating copy to clipboard operation
react-instantsearch copied to clipboard

useMenu does not work properly when filtering for one level of an otherwise hierarchical facet

Open bartosz-urban-danfoss opened this issue 2 years ago • 2 comments

🐛 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:

  1. Go to [this sandbox](https://codesandbox.io/s/youthful-tesla-99xsuz?file=/components/MenuUsingHierarchical.tsx)
  2. Results as expected when using a custom transformItems, that takes this data from _rawResults
  3. Comment out transformItems
  4. There will be no results 🐛
  5. Notice that useEffect which happens on mount does not apply filtering at all, despite creating a tag 🐛
  6. Replace attribute in useMenu 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",

bartosz-urban-danfoss avatar Jun 29 '22 17:06 bartosz-urban-danfoss

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?

Haroenv avatar Jul 04 '22 09:07 Haroenv

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.

bartosz-urban-danfoss avatar Jul 04 '22 10:07 bartosz-urban-danfoss

I think I have the same problem.

joker-777 avatar Nov 16 '22 12:11 joker-777

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.

joker-777 avatar Nov 16 '22 12:11 joker-777

@Haroenv Cany idea when this bug gets priority? @bartosz-urban-danfoss Were you able to find a workaround?

joker-777 avatar Nov 22 '22 14:11 joker-777

@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 avatar Nov 22 '22 14:11 Haroenv

@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 avatar Nov 22 '22 14:11 bartosz-urban-danfoss

@bartosz-urban-danfoss Thanks a lot. I will try it out as well.

joker-777 avatar Nov 22 '22 15:11 joker-777

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

FabienMotte avatar Dec 21 '22 13:12 FabienMotte