Autocomplete component

Autocomplete component

Out there are so many ways to create an Autocomplete and, you'll face several scenarios of it along your career as "frontender". Here I wanna post some thoughts about this component and, how to code it without adding extra dependencies, I suppose your stack is: React and, Tailwind. The idea of this post is help us to think how to create a component.

What is an Autocomplete component?

In essential it's an interactive input, while the the user is writing text in the box, a list of "coincidences" is displayed above or, below the input, the list shouldn't push down the content below aaand, the "coincidences" is a list of filtered elements using the input value as query.

Components structure

In this implementation the parent component will provide all filtered options, and the Autocomplete will pass the data through callbacks to the parent. So component tree will be like: Component structure

The component API

Let's define an API to interact with, reading the definition above, something like:

interface AutocompleteProps<TOptions> {
  options: TOptions[];
  onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onSelect: (option: TOption) => void;
  className?: string;
}

These are the most basic props to create an Autocomplete, once it gets working you'll be able to enhance the implementation.

The Parent

The next thing we gonna do is define the Parent component.

const Parent = () => {
  const [inputValue, setInputValue] = useState<string>()
  const [selectedOption, setSelectedOption] = useState()

  // get the data from somewhere
  const { data } = useGetData()

  const onSelectOption = (options) => {
    setSelectedOption(option)
  }

  return (
    <>
      <h1>Selected Value: </h1>
      <span>{JSON.stringify(selectedOption)}</span>

      <Autocomplete
        options={data}
        onChangeInput={setInputValue}
        onSelect={onSelectOption}
      />
    </>
  )
}

Easy! right? Now collecting all of these definitions we can actually code our Autocomplete (finally!).

The Autocomplete Key

The Autocomplete has 2 base elements, an input and a list of results, and 1 UX constrain, the list must not push down the content below 👍. To address that constrain we gonna use position relative and position absolute like following:

{/* Render the list of coincidences */}
<div className="relative">
  {isOpen ? (
    <ol className="absolute top-0">
    ...

By doing that, we fix that the position of the options list. Now wrapping the input and the options with an element to set the width.

const Autocomplete = <TOption>({
  options,
  onInputChange,
  onSelect,
}: AutocompleteProps) => {
  const [isOpen, setIsOpen] = useState(false);

  const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
    setIsOpen(true);
  }
  const onBlurHandler = () => setIsOpen(false);

  return (
    <div className={className}>
      <input
        onChange={onInputChange}
        onFocus={onFocusHandler}
        onBlur={onBlurHandler}
      />

      {/* this is KEY! */}
      <div className="relative mt-1">
        {isOpen ? (
          <ol className="absolute top-0 animate-in fade-in-0 fade-out-0">
            {options?.map((opt, idx) => {
              return (
                <li
                  onClick={() => {
                    setIsOpen(false)
                    onSelect(opt)
                  }}
                >
                {/* Render the element */ }
                  Element {idx}
                </li>
              )
            })}
          </ol>
        ) : null}
      </div>
    </div>
  )
}

For me, the key to understand how to code this component in the future is the position of the list of options using the positions relative and absolute. It's obvious that there are many other things to take into account and, libraries, like MateriaUI, provide full Autocomplete implementations, but using these UI libraries may lead you to not think about details.

Conclusion

With this way of thinking we can scale our implementation to something as complex as wanted, like adding Loading and Empty states, select elements using arrow keys, etc. The "coincidences" we talked before are elements that matches somehow with the input text, I prefer to filter the elements outside of the Autocomplete component to avoid coupling the concepts and, the parent can use the selected value as it desires, fetch the data from anywhere and, having a controlled AUtocomplete component you will be able to control the input value. From this point of view, there are not many differences between an Autocomplete and a Dropdown component.

Hope this help you! 🙂