How to create autocomplete with react-autocomplete-pure
Tags: typescript, react, autocomplete
Published: 2022-02-01 16:23:17

Autocomplete in input fields is a very useful feature that allows customers to improve their UX when using your site.
One of the features of autocomplete is that we help the user to enter some data, not forcing him to enter the entire text, but by providing a ready-made set of options. Thanks to this approach, the user can choose exactly from those options that the application knows how to work with, saving us from unnecessary checks and errors.

One of the first ready-made solutions that come to mind are libraries such as [react-autocomplete](https://github.com/reactjs/react-autocomplete) and [react-autosuggest](https://github.com/moroshko/react-autosuggest). These are great libraries that do exactly what they are supposed to do: when you enter a value in the input field, they display a dropdown with possible options for substitution. Unfortunately, these libraries are no longer actively supported by their maintainers (`react-autosuggest` is looking for the main maintainer, and `react-autocomplete` is in the archive).

In this regard, I decided to write (yes, this is sooo classic :smile:) my vision of a component for autocompletion.

Let me introduce [react-autocomplete-pure](https://github.com/qramilq/react-autocomplete-pure) - TypeScript friendly react component for autocomplete. 
The main features that I wanted to put into this component is that I developed it with the ability to have the finest possible configuration of everything that may be required when developing specifically for your project.

Below is the main key features which `react-autocomplete-pure` gives to you:
* the maximum setting for displaying all parts of the component (input field, list, managing the view of the list container and its composition);
* written in TypeScript, which makes it possible to take full advantage of typings with support for generics;
* keyboard events support;
* a11y support;

`react-autocomplete-pure` has almost no state of its own, which means it needs to be managed in the parent component. This keeps the component as dummy as possible, which allows us to keep all the logic in one place and will only manage the display based on passed props.

## Example of using
Suppose the user wants to enter in our input field the name of a movie, from the imdb's top 100 movies. Well, there is nothing easier! Let's add a field for autocompletion and show the user the available movie options as they type.

First, let's install `react-autocomplete-pure` to our project
using `npm`

```sh
npm i react-autocomplete-pure
```

or via `yarn`

```sh
yarn add react-autocomplete-pure
```
> **_NOTE:_**  Before continuing, I would like to clarify that the code below is written in TypeScript so that we have type support and better DX when working with our IDE. Optionally, types can be omitted if you want to write in Vanilla JS.

We know that movies will come to us as an array of objects from our backend ([example](https://github.com/qramilq/react-autocomplete-pure/blob/master/site/mock.ts#L4)). Each object in this array is a movie with its title and release year.
```ts
type Film = { title: string; year: number };
const topFilms: Film[] = [
  { title: "The Shawshank Redemption", year: 1994 },
  { title: "The Godfather", year: 1972 },
  /*...and more...*/
];
```
So, as we know incoming data format, now it's time to add the component to the project:
```tsx
import { AutocompletePure } from "react-autocomplete-pure";
import { Film } from './types';

export function App() {
  return (
    <div>
      <h1>My awesome app with autocomplete</h1>
      <AutocompletePure<Film>  /*what about required props?*/>
    </div>
  );
}
```
We've added a component to the project, but we haven't added any props to it yet. Let's fix this.
According to the available props in [documentation](https://github.com/qramilq/react-autocomplete-pure#props) we have some required props.
Don't be afraid that there are so many of them, they are all intuitive and it is thanks to them that you can fully control the behavior of the component :smile:. Let's update our code.
 > **_NOTE:_** `fetchFilms` function is taken from [here](https://github.com/qramilq/react-autocomplete-pure/blob/master/site/mock.ts#L131).

```tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { AutocompletePure, RenderItem } from "react-autocomplete-pure";
import { fetchFilms } from "./mock";
import { Film } from "./types";

// let's add some style if item is highlighted
const renderItem: RenderItem<Film> = (item, { isHighlighted }) => (
  <span style={{ fontWeight: isHighlighted ? 700 : 400 }}>{item.title}</span>
);

// Needs to get new value when using keyboard events
const getSuggestionValue = (item: Film) => item.title;

export function App() {
  const [isOpen, setIsOpen] = useState<boolean>(false);
  const [suggestions, setSuggestions] = useState<Film[]>([]);
  const [value, setValue] = useState<string>("");

  // When input changes then save value
  // If change reason is type on input then get new items, save them and close dropdown if no new items fetched
  // If change reason is enter keydown then simple close dropdown
  const handleChange: AutocompletePureProps<Film>["onChange"] = useCallback(
    async (_event, { value, reason }) => {
      setValue(value);
      if (reason === "INPUT") {
        const newFilms = await fetchFilms(value);
        setSuggestions(newFilms);
        setIsOpen(Boolean(newFilms.length));
      } else if (reason === "ENTER") {
        setIsOpen(false);
      }
    },
    []
  );

  // When item selected then save it and close dropdown
  const handleSelect: AutocompletePureProps<Film>["onSelect"] = useCallback(
    (_event, { item }) => {
      const value = getSuggestionValue(item);
      setValue(value);
      setIsOpen(false);
    },
    []
  );

  return (
    <div>
      <h1>My awesome app with autocomplete</h1>
      <AutocompletePure<Film>
        open={isOpen}
        value={value}
        items={suggestions}
        onChange={handleChange}
        onSelect={handleSelect}
      />
    </div>
  );
}
```
Our component is almost ready to use, except that we currently do not hide the list if we click somewhere outside the component. This is easy to fix, the component can calls the `onClickOutside` callback, in which we can implement the logic for hiding the list.

```tsx
/* ...same as before... */
export function App() {
  /* ...same as before... */
  const handleClickOutside = useCallback((_event: Event) => {
    setIsOpen(false);
  }, []);

  /* ...same as before... */
  return (
    <AutocompletePure<Film>
      open={isOpen}
      value={value}
      items={suggestions}
      onChange={handleChange}
      onSelect={handleSelect}
      onClickOutside={handleClickOutside}
    />
  );
  /* ...same as before... */
}
```
That's all, now the component can be fully used! Congratulations, you did it! You can play more in sandbox: {% codesandbox vibrant-field-mmrow %}

If you want to see more features (like custom renderers for component's parts) of using component you can watch them in repository in [site folder](https://github.com/qramilq/react-autocomplete-pure/blob/master/site/App.tsx)