TypeError when using the createQuerySuggestionsPlugin

I try to setup autocomplete with some filtering and all the stuff one usually want in a search experience. I do struggle some to get it working. I’m using nextjs with react 18+, and it feels like the docs is not complete with that configuration, or maybe I’m missing something.

Note that I have it working just fine until I add the createQuerySuggestionsPlugin. The code I have is based on the code from the docs as well as the examples since I had to take from both to get it working with my configuration. My Autocomplete component look like:

import React from "react";
import { createElement, Fragment, useEffect, useRef, useState, useMemo } from "react";

import { usePagination, useSearchBox } from "react-instantsearch-hooks";
import { autocomplete,getAlgoliaResults, AutocompleteOptions, AutocompleteComponents } from "@algolia/autocomplete-js";
import { BaseItem } from "@algolia/autocomplete-core";
import { createLocalStorageRecentSearchesPlugin } from "@algolia/autocomplete-plugin-recent-searches";
import { createQuerySuggestionsPlugin } from "@algolia/autocomplete-plugin-query-suggestions";

import "@algolia/autocomplete-theme-classic";
import { createRoot, Root } from "react-dom/client";
import { ProductHit } from "../lib/prooduct/types";

type AutocompleteProps = Partial<AutocompleteOptions<BaseItem>> & {
  className?: string;
  searchClient: any
};

type SetInstantSearchUiStateOptions = {
  query: string;
  category?: string;
};

import {
  NEXTECOM_HIERARCHICAL_ATTRIBUTES,
  NEXTECOM_INDEX_NAME,
  NEXTECOM_QUERY_SUGGESTIONS,
} from '../constants';
import { useHierarchicalMenu } from "react-instantsearch-hooks-web";

export const TAXONOMY_HIERARCHICAL_ATTRIBUTES = [
  'taxonomy.level1',
  'taxonomy.level2',
  'taxonomy.level3',
  'taxonomy.level4',
];

export function Autocomplete({
  className,
  searchClient,
  ...autocompleteProps
}: AutocompleteProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const panelRootRef = useRef<Root | null>(null);
  const rootRef = useRef<HTMLElement | null>(null);

  const { query, refine: setQuery } = useSearchBox();
  const { items: categories, refine: setCategory } = useHierarchicalMenu({
    attributes: NEXTECOM_HIERARCHICAL_ATTRIBUTES,
  });  
  const { refine: setPage } = usePagination();

  const [instantSearchUiState, setInstantSearchUiState] = useState<
    SetInstantSearchUiStateOptions
  >({ query });

  useEffect(() => {
    setQuery(instantSearchUiState.query);
    instantSearchUiState.category && setCategory(instantSearchUiState.category);
    setPage(0);
  }, [instantSearchUiState]);

  const currentCategory = useMemo(
    () => categories.find(({ isRefined }) => isRefined)?.value,
    [categories]
  );

  const plugins = useMemo(() => {
    const recentSearches = createLocalStorageRecentSearchesPlugin({
      key: "nextecom-searches",
      limit: 3,
      transformSource({ source }) {
        return {
          ...source,
          onSelect({ item }) {
            setInstantSearchUiState({ query: item.label, category: item.category });
          }
        };
      }
    });

    const querySuggestions = createQuerySuggestionsPlugin({
      searchClient,
      indexName: NEXTECOM_QUERY_SUGGESTIONS,
      getSearchParams() {
        if (!currentCategory) {
          return recentSearches.data!.getAlgoliaSearchParams({
            hitsPerPage: 6,
          });
        }

        return recentSearches.data!.getAlgoliaSearchParams({
          hitsPerPage: 3,
          facetFilters: [
            `${NEXTECOM_INDEX_NAME}.facets.exact_matches.${NEXTECOM_HIERARCHICAL_ATTRIBUTES[0]}.value:-${currentCategory}`,
          ],
        });
      },
      categoryAttribute: [
        NEXTECOM_INDEX_NAME,
        'facets',
        'exact_matches',
        NEXTECOM_HIERARCHICAL_ATTRIBUTES[0],
      ],
      transformSource({ source }) {
        return {
          ...source,
          sourceId: 'querySuggestionsPlugin',
          onSelect({ item }) {
            setInstantSearchUiState({
              query: item.query,
              category: item.__autocomplete_qsCategory || '',
            });
          },
          getItems(params) {
            if (!params.state.query) {
              return [];
            }
            const items = source.getItems(params);
            return items;
          },
          templates: {
            ...source.templates,
            header({ items }) {
              console.log("Some header: ", items);
              if (!currentCategory || items.length === 0) {
                return <Fragment />;
              }

              return (
                <Fragment>
                  <span className="aa-SourceHeaderTitle">
                    In other categories
                  </span>
                  <span className="aa-SourceHeaderLine" />
                </Fragment>
              );
            },
          },
        };
      },
    });
    return [recentSearches, querySuggestions];
  }, []);


  useEffect(() => {
    if (!containerRef.current) {
      return undefined;
    }

    const search = autocomplete<ProductHit>({
      container: containerRef.current,
      placeholder: 'Search',
      getSources({ query }) {
        return [
          {
            sourceId: 'products',
            getItems() {
              return getAlgoliaResults<ProductHit>({
                searchClient,
                queries: [
                  {
                    indexName: 'nextecom',
                    query,
                  },
                ],
              });
            },
            templates: {
              item({ item, components }) {
                return <ProductItem hit={item} components={components} />;
              },
              noResults() {
                return 'No products matching.';
              },
            },
          },
        ];
      },
      renderer: { createElement, Fragment, render: () => {} },
      render({ children }, root) {
        if (!panelRootRef.current || rootRef.current !== root) {
          rootRef.current = root;

          panelRootRef.current?.unmount();
          panelRootRef.current = createRoot(root);
        }

        panelRootRef.current.render(children);
      },
      initialState: { query },
      onReset() {
        setInstantSearchUiState({ query: "", category: undefined });
      },
      onSubmit({ state }) {
        setInstantSearchUiState({ query: state.query });
      },
      onStateChange({ prevState, state }) {
        if (prevState.query !== state.query) {
          setInstantSearchUiState({
            query: state.query
          });
        }
      },
      plugins
    });

    return () => {
      search.destroy();
    };
  }, []);


  return <div ref={containerRef} />;
}

type ProductItemProps = {
  hit: ProductHit;
  components: AutocompleteComponents;
};

function ProductItem({ hit, components }: ProductItemProps) {
  return (
    <div className="aa-ItemWrapper">
      <div className="aa-ItemContent">
        <div className="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop">
          <img src={hit.imageFullUrl} alt={hit.title} width="40" height="40" />
        </div>

        <div className="aa-ItemContentBody">
          <div className="aa-ItemContentTitle">
            <components.Highlight hit={hit} attribute="title" />
          </div>
          <div className="aa-ItemContentDescription">
            By <strong>{hit.brand}</strong> in{' '}
            <strong>{hit.mainTaxonomy}</strong>
          </div>
        </div>
      </div>
    </div>
  );
}

Looking at the response that comes back I do see that I don’t get any taxonomy facets back in my search, I guess that is the problem… I’m not sure why I don’t get it though since I do think it should align with the examples I’ve found.

The error message I have is this one:

I thought I was on to something today since I had forgot to enable “Queries by facets”, but that didn’t help. Below is a screenshot of the result I have. I guess I have the map of undefined since exact_matches isn’t populated with my facet.

image

I think I figured it out. To get it working I also needed to enable the categories on the suggestion index, now I get the data I want.