Building an autocomplete-input component in Next.js
Tags: nextjs, tailwindcss, autocomplete, input
Published: 2023-10-06 12:48:14

[Sandbox Demo](https://codesandbox.io/p/github/BanerjeeProdipta/autocomplete-input/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clnevmydh0007356kxemwi9ic%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clnevmydh0003356kazmpjdp6%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clnevmydh0005356ki9xrfbsy%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clnevmydh0006356kier50zhn%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B60%252C40%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clnevmydh0003356kazmpjdp6%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clnevmydh0002356k2k1syura%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clnevmydh0003356kazmpjdp6%2522%252C%2522activeTabId%2522%253A%2522clnevmydh0002356k2k1syura%2522%257D%252C%2522clnevmydh0006356kier50zhn%2522%253A%257B%2522id%2522%253A%2522clnevmydh0006356kier50zhn%2522%252C%2522activeTabId%2522%253A%2522clnevoloi00g5356k2nmw1ztg%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522TASK_PORT%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522port%2522%253A3000%252C%2522id%2522%253A%2522clnevoloi00g5356k2nmw1ztg%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522path%2522%253A%2522%252F%2522%257D%255D%257D%252C%2522clnevmydh0005356ki9xrfbsy%2522%253A%257B%2522id%2522%253A%2522clnevmydh0005356ki9xrfbsy%2522%252C%2522activeTabId%2522%253A%2522clnevmydh0004356kcdzqhnbf%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clnevmydh0004356kcdzqhnbf%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TERMINAL%2522%252C%2522shellId%2522%253A%2522clnfww0x2000te6ft3j2i7kld%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522id%2522%253A%2522clnevoi3900em356kvnt0021k%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D)

Introduction:
AutoComplete input fields are a common user interface element used to help users efficiently find and select options from a predefined list. In this blog, we'll explore how to create a customizable AutoComplete input component in React with throttling to improve performance. We'll walk through the code for the component and explain its key features.

Creating the AutoCompleteInput Component:
The `AutoCompleteInput` component is designed to provide a user-friendly AutoComplete experience. Let's break down its important components and functionality:

1. **Props**:
   The component takes two props: `options` and `handleSelection`. The `options` prop is an array of strings representing the available options, while `handleSelection` is a callback function to handle the selected option.

2. **State and Refs**:
   - `inputRef`: This useRef hook is used to reference the input element for handling clicks outside the component.
   - `value`: Represents the current value of the input field.
   - `suggestions`: Stores the list of suggestions based on the user's input.

3. **getSuggestions Function**:
   This function filters the available options based on the user's input. It converts both the input and options to lowercase for case-insensitive matching.

4. **onChange Function**:
   Whenever the input field's value changes, this function updates the `value` state and recalculates the suggestions based on the new value.

5. **onSuggestionClick Function**:
   When a suggestion is clicked, this function sets the selected suggestion as the input value and calls the `handleSelection` callback with the selected value.

6. **isSuggestionEmpty Function**:
   This function checks if the suggestions list is empty or contains only an empty string. It is used to conditionally render the suggestions dropdown.

7. **Event Listener for Clicks Outside**:
   An `useEffect` hook is used to add a click event listener to the document body. This listener detects clicks outside of the component, allowing it to blur and hide the suggestions dropdown.

8. **Throttling with Lodash**:
   To improve performance and responsiveness, we've added throttling to the `onChange` event using the `lodash/debounce` function. This reduces the frequency of function calls while the user is typing rapidly.

Installing Lodash:
Before implementing throttling, make sure to install the `lodash` library using npm. Run the following command in your project directory:

```bash
npm install lodash
```

Rendering the Component:
The `AutoCompleteInput` component is rendered within the `Home` component. When the user selects a suggestion, the selected value is logged to the console.

Conclusion:
Creating an AutoComplete input component in React can enhance user experience when searching or selecting items from a list. The provided code demonstrates a basic implementation, and with the added throttling, it ensures smooth performance even with rapid typing.

Feel free to use this code as a starting point and adapt it to your specific needs. By following the installation instructions for lodash, you can easily add throttling to your React components. Happy coding!

---

```
"use client";
import AutoCompleteInput from "./components/AutoComplete";

const data = ["One", "Two", "Three"];

export default function Home() {
  const handleSelection = (selectedOption: string) => {
    console.log({ Selected: { selectedOption } });
  };

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      <AutoCompleteInput
        options={data.map((v: string) => v)}
        handleSelection={handleSelection}
      />
    </main>
  );
}

```

```
import React, { useEffect, useRef, useState } from "react";
import debounce from "lodash/debounce"; // npm install lodash

interface IPropType {
  options: string[];
  handleSelection: (val: string) => void;
}

const AutoCompleteInput = (props: IPropType) => {
  const { options, handleSelection } = props;
  const inputRef = useRef<HTMLInputElement>(null);
  const [value, setValue] = useState("");
  const [suggestions, setSuggestions] = useState([""]);

  const getSuggestions = (inputValue: string) => {
    if (typeof inputValue !== "string") {
      return [];
    }
    const inputValueLowerCase = inputValue.toLowerCase();
    return options.filter((option) =>
      option.toLowerCase().includes(inputValueLowerCase),
    );
  };

  // Debounce the onChange function
  const debouncedOnChange = debounce((newValue: string) => {
    setValue(newValue);
    setSuggestions(getSuggestions(newValue));
  }, 100); // Adjust the debounce delay as needed (e.g., 300 milliseconds)

  const onSuggestionClick = (suggestion: string) => {
    setValue(suggestion);
    handleSelection(suggestion);
    setSuggestions([]);
  };

  const isSuggestionEmpty = () => {
    if (suggestions.length === 1 && suggestions[0] === "") {
      return true;
    } else return false;
  };

  // Add a click event listener to the document body to handle clicks outside of the component
  useEffect(() => {
    const handleDocumentClick = (e: any) => {
      if (inputRef.current && !inputRef.current.contains(e.target)) {
        inputRef.current.blur();
        setSuggestions([]);
      }
    };

    document.addEventListener("click", handleDocumentClick);

    return () => {
      document.removeEventListener("click", handleDocumentClick);
    };
  }, []);

  return (
    <div className="relative">
      <input
        ref={inputRef}
        className="w-full border border-dark text-black transition-all duration-300 rounded-md px-4 py-3 focus:outline-none"
        type="text"
        placeholder="Search"
        value={value}
        onChange={(e) => debouncedOnChange(e.target.value)}
        onFocus={() => {
          setSuggestions(options);
          setValue("");
        }}
      />
      {!isSuggestionEmpty() && suggestions.length > 0 && (
        <ul
          className="bg-white border-blue-500 border-2 rounded hover:cursor-pointer absolute top-14 w-full z-20 max-h-64 overflow-y-auto"
          onPointerLeave={() => {
            inputRef?.current?.blur();
            setSuggestions([""]);
          }}
        >
          {suggestions.map((suggestion) => (
            <li
              key={suggestion}
              className="hover:bg-blue-500 hover:text-white transition duration-200 text-sm text-gray-700 p-1"
              onClick={() => onSuggestionClick(suggestion)}
            >
              {suggestion}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default AutoCompleteInput;


```