DEV Community

Cover image for Building an autocomplete-input component in Next.js
Prodipta Banerjee
Prodipta Banerjee

Posted on

Building an autocomplete-input component in Next.js

Sandbox Demo

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:

npm install lodash
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode
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;


Enter fullscreen mode Exit fullscreen mode

Top comments (0)