Skip to content

How to create algolia autocomplete custom renderer using react class component

I am tickling with Algolia autocomplete, and I am trying to replicate their custom renderer in react using the class component. This is the sandbox of the minimal demo of custom renderer using functional component,

and here is my attempt to convert it into a class component.

import { createAutocomplete } from "@algolia/autocomplete-core";
import { getAlgoliaResults } from "@algolia/autocomplete-preset-algolia";
import algoliasearch from "algoliasearch/lite";
import React from "react";

const searchClient = algoliasearch(
  "latency",
  "6be0576ff61c053d5f9a3225e2a90f76"
);
// let autocomplete;
class AutocompleteClass extends React.PureComponent {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
    this.autocomplete = null;
    this.state = {
      autocompleteState: {},
    };
  }

  componentDidMount() {
    if (!this.inputRef.current) {
      return undefined;
    }

    this.autocomplete = createAutocomplete({
      onStateChange({ state }) {
        // (2) Synchronize the Autocomplete state with the React state.
        this.setState({ autocompleteState: state });
      },
      getSources() {
        return [
          {
            sourceId: "products",
            getItems({ query }) {
              return getAlgoliaResults({
                searchClient,
                queries: [
                  {
                    indexName: "instant_search",
                    query,
                    params: {
                      hitsPerPage: 5,
                      highlightPreTag: "<mark>",
                      highlightPostTag: "</mark>",
                    },
                  },
                ],
              });
            },
            getItemUrl({ item }) {
              return item.url;
            },
          },
        ];
      },
    });
  }

  render() {
    const { autocompleteState } = this.state;
    return (
      <div className="aa-Autocomplete" {...this.autocomplete?.getRootProps({})}>
        <form
          className="aa-Form"
          {...this.autocomplete?.getFormProps({
            inputElement: this.inputRef.current,
          })}
        >
          <div className="aa-InputWrapperPrefix">
            <label
              className="aa-Label"
              {...this.autocomplete?.getLabelProps({})}
            >
              Search
            </label>
          </div>
          <div className="aa-InputWrapper">
            <input
              className="aa-Input"
              ref={this.inputRef}
              {...this.autocomplete?.getInputProps({})}
            />componentDidUpdate()
          </div>
        </form>
        <div className="aa-Panel" {...this.autocomplete?.getPanelProps({})}>
          {autocompleteState.isOpen &&
            autocompleteState.collections.map((collection, index) => {
              const { source, items } = collection;

              return (
                <div key={`source-${index}`} className="aa-Source">
                  {items.length > 0 && (
                    <ul
                      className="aa-List"
                      {...this.autocomplete?.getListProps()}
                    >
                      {items.map((item) => (
                        <li
                          key={item.objectID}
                          className="aa-Item"
                          {...this.autocomplete?.getItemProps({
                            item,
                            source,
                          })}
                        >
                          {item.name}
                        </li>
                      ))}
                    </ul>
                  )}
                </div>
              );
            })}
        </div>
      </div>
    );
  }
}

export default AutocompleteClass;

and the sandbox of the same version, I also tried using componentDidUpdate() but no luck, any lead where I did wrong would be much appreciated thank you 🙂

Answer

Ok, dont know why you need it made into class component but here you go:

import { createAutocomplete } from "@algolia/autocomplete-core";
import { getAlgoliaResults } from "@algolia/autocomplete-preset-algolia";
import algoliasearch from "algoliasearch/lite";
import React from "react";

const searchClient = algoliasearch(
  "latency",
  "6be0576ff61c053d5f9a3225e2a90f76"
);
// let autocomplete;
class AutocompleteClass extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      autocompleteState: {},
      query: '',
    };
    this.autocomplete = createAutocomplete({
      onStateChange: this.onChange,
      getSources() {
        return [
          {
            sourceId: "products",
            getItems({ query }) {
              console.log('getting query', query)
              return getAlgoliaResults({
                searchClient,
                queries: [
                  {
                    indexName: "instant_search",
                    query,
                    params: {
                      hitsPerPage: 5,
                      highlightPreTag: "<mark>",
                      highlightPostTag: "</mark>"
                    }
                  }
                ]
              });
            },
            getItemUrl({ item }) {
              return item.url;
            }
          }
        ];
      }
    });
  }

  onChange = ({ state }) => {
    console.log(state)
    this.setState({ autocompleteState: state, query: state.query });
  }

  render() {
    const { autocompleteState } = this.state;
    return (
      <div className="aa-Autocomplete" {...this.autocomplete?.getRootProps({})}>
        <form
          className="aa-Form"
          {...this.autocomplete?.getFormProps({
            inputElement: this.state.query
          })}
        >
          <div className="aa-InputWrapperPrefix">
            <label
              className="aa-Label"
              {...this.autocomplete?.getLabelProps({})}
            >
              Search
            </label>
          </div>
          <div className="aa-InputWrapper">
            <input
              className="aa-Input"
              value={this.state.query}
              {...this.autocomplete?.getInputProps({})}
            />
          </div>
        </form>
        <div className="aa-Panel" {...this.autocomplete?.getPanelProps({})}>
          {autocompleteState.isOpen &&
            autocompleteState.collections.map((collection, index) => {
              const { source, items } = collection;

              return (
                <div key={`source-${index}`} className="aa-Source">
                  {items.length > 0 && (
                    <ul
                      className="aa-List"
                      {...this.autocomplete?.getListProps()}
                    >
                      {items.map((item) => (
                        <li
                          key={item.objectID}
                          className="aa-Item"
                          {...this.autocomplete?.getItemProps({
                            item,
                            source
                          })}
                        >
                          {item.name}
                        </li>
                      ))}
                    </ul>
                  )}
                </div>
              );
            })}
        </div>
      </div>
    );
  }
}

export default AutocompleteClass;

Anyway the componentDidMount is called only once, and because of ref object is undefined it just returned from it. Also messing with this in class components is quite a bad idea (that is why func components are recommended)