algolia / autocomplete

🔮 Fast and full-featured autocomplete library
https://alg.li/autocomplete
MIT License
5.03k stars 329 forks source link

"insight:true" shows error in createAutocomplete with NextJS React18 #1218

Closed Hamlet626 closed 10 months ago

Hamlet626 commented 11 months ago

Description

It shows the following error when making “insights:true” in createAutocomplete:

 ⨯ node_modules/@algolia/autocomplete-plugin-algolia-insights/dist/umd/index.production.js (2:4254) @ h
 ⨯ TypeError: Cannot read properties of undefined (reading ‘version’)
    at eval (./src/app/test/autocomplete.tsx:29:161)
    at Autocomplete (./src/app/test/autocomplete.tsx:29:71)

my package.json

{
  “name”: “tester”,
  “version”: “0.1.0”,
  “private”: true,
  “scripts”: {
    “dev”: “next dev”,
    “build”: “next build”,
    “start”: “next start”,
    “lint”: “next lint”
  },
  “dependencies”: {
    “@algolia/autocomplete-core”: “^1.12.1",
    “@algolia/autocomplete-preset-algolia”: “^1.12.1",
    “next”: “14.0.1",
    “react”: “^18",
    “react-dom”: “^18",
    “react-instantsearch-nextjs”: “^0.1.4"
  },
  “devDependencies”: {
    “@types/node”: “^20”,
    “@types/react”: “^18”,
    “@types/react-dom”: “^18”,
    “autoprefixer”: “^10.0.1”,
    “eslint”: “^8”,
    “eslint-config-next”: “14.0.1”,
    “postcss”: “^8”,
    “tailwindcss”: “^3.3.0”,
    “typescript”: “^5”
  }
}

autocomplete.tsx

import {
    AutocompleteOptions,
    AutocompleteState,
    createAutocomplete,
  } from ‘@algolia/autocomplete-core’;
  import { getAlgoliaResults } from ‘@algolia/autocomplete-preset-algolia’;
  import { Hit } from ‘@algolia/client-search’;
  import algoliasearch from ‘algoliasearch/lite’;
  import React from ‘react’;

  const searchClient = algoliasearch(
    ‘latency’,
    ‘6be0576ff61c053d5f9a3225e2a90f76’
  );

  type AutocompleteItem = Hit<{
    brand: string;
    categories: string[];
    image: string;
    name: string;
    objectID: string;
    url: string;
  }>;

  export function Autocomplete(
    props: Partial<AutocompleteOptions<AutocompleteItem>>
  ) {
    const [autocompleteState, setAutocompleteState] = React.useState<
      AutocompleteState<AutocompleteItem>
    >({
      collections: [],
      completion: null,
      context: {},
      isOpen: false,
      query: ‘’,
      activeItemId: null,
      status: ‘idle’,
    });
    const autocomplete = React.useMemo(
      () =>
        createAutocomplete<
          AutocompleteItem,
          React.BaseSyntheticEvent,
          React.MouseEvent,
          React.KeyboardEvent
        >({
          onStateChange({ state }) {
            setAutocompleteState(state);
          },
          insights:true,
          getSources() {
            return [
              {
                sourceId: ‘products’,
                getItems({ query }) {
                  return getAlgoliaResults({
                    searchClient,
                    queries: [
                      {
                        indexName: ‘instant_search’,
                        query,
                        params: {
                          hitsPerPage: 5,
                        },
                      },
                    ],
                  });
                },
                getItemUrl({ item }) {
                  return item.url;
                },
              },
            ];
          },
          ...props,
        }),
      [props]
    );
    const inputRef = React.useRef<HTMLInputElement>(null);
    const formRef = React.useRef<HTMLFormElement>(null);
    const panelRef = React.useRef<HTMLDivElement>(null);
    const { getEnvironmentProps } = autocomplete;

    React.useEffect(() => {
      if (!formRef.current || !panelRef.current || !inputRef.current) {
        return undefined;
      }

      const { onTouchStart, onTouchMove, onMouseDown } = getEnvironmentProps({
        formElement: formRef.current,
        inputElement: inputRef.current,
        panelElement: panelRef.current,
      });

      window.addEventListener(‘mousedown’, onMouseDown);
      window.addEventListener(‘touchstart’, onTouchStart);
      window.addEventListener(‘touchmove’, onTouchMove);

      return () => {
        window.removeEventListener(‘mousedown’, onMouseDown);
        window.removeEventListener(‘touchstart’, onTouchStart);
        window.removeEventListener(‘touchmove’, onTouchMove);
      };
    }, [getEnvironmentProps, autocompleteState.isOpen]);

    return (
      <div className=“aa-Autocomplete” {...autocomplete.getRootProps({})}>
        <form
          ref={formRef}
          className=“aa-Form”
          {...autocomplete.getFormProps({ inputElement: inputRef.current })}
        >
          <div className=“aa-InputWrapperPrefix”>
            <label className=“aa-Label” {...autocomplete.getLabelProps({})}>
            </label>
          </div>
          <div className=“aa-InputWrapper”>
            <input
              className=“aa-Input”
              ref={inputRef}
              {...autocomplete.getInputProps({ inputElement: inputRef.current })}
            />
          </div>
          <div className=“aa-InputWrapperSuffix”>
          </div>
        </form>

        {autocompleteState.isOpen && (
          <div
            ref={panelRef}
            className={[
              ‘aa-Panel’,
              ‘aa-Panel--desktop’,
              autocompleteState.status === ‘stalled’ && ‘aa-Panel--stalled’,
            ]
              .filter(Boolean)
              .join(' ‘)}
            {...autocomplete.getPanelProps({})}
          >
            <div className=“aa-PanelLayout aa-Panel--scrollable”>
              {autocompleteState.collections.map((collection, index) => {
                const { source, items } = collection;

                return (
                  <section key={`source-${index}`} className=“aa-Source”>
                    {items.length > 0 && (
                      <ul className=“aa-List” {...autocomplete.getListProps()}>
                        {items.map((item) => {
                          return (
                            <li
                              key={item.objectID}
                              className=“aa-Item”
                              {...autocomplete.getItemProps({ item, source })}
                            >
                              <div className=“aa-ItemWrapper”>
                                <div className=“aa-ItemContent”>
                                  <div className=“aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop”>
                                    <img
                                      src={item.image}
                                      alt={item.name}
                                      width=“40"
                                      height=“40”
                                    />
                                  </div>
                                  <div className=“aa-ItemContentBody”>
                                    <div className=“aa-ItemContentDescription”>
                                      By <strong>{item.brand}</strong> in{’ ’}
                                      <strong>{item.categories[0]}</strong>
                                    </div>
                                  </div>
                                </div>
                                <div className=“aa-ItemActions”>
                                  <button
                                    className=“aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly”
                                    type=“button”
                                    title=“Select”
                                    style={{ pointerEvents: ‘none’ }}
                                  >
                                    <svg fill=“currentColor” viewBox=“0 0 24 24”>
                                      <path d=“M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z” />
                                    </svg>
                                  </button>
                                </div>
                              </div>
                            </li>
                          );
                        })}
                      </ul>
                    )}
                  </section>
                );
              })}
            </div>
          </div>
        )}
      </div>
    );
  }

Reproduction

I just built a test project for integrating autocomplete with nextJS, like this:

Demo →

Run the Repository →: npm run dev go to localhost:3000/test

dhayab commented 10 months ago

Hi, thanks for your report.

I can confirm there seems to be an issue with insights when enabled during server-side rendering environment. While we work on fixing this problem in the library, you could try a workaround by enabling insights in browser environment only:

const isBrowser = typeof window !== 'undefined';

createAutocomplete({
  /* ... */,
  insights: isBrowser,
});
Hamlet626 commented 10 months ago

Hi, thanks for your report.

I can confirm there seems to be an issue with insights when enabled during server-side rendering environment. While we work on fixing this problem in the library, you could try a workaround by enabling insights in browser environment only:

const isBrowser = typeof window !== 'undefined';

createAutocomplete({
  /* ... */,
  insights: isBrowser,
});

It works for me, thanks very much for the solution!