What is the point of having high performance computers accessible right in the palm of our hand if we need to type everyting verbatim, in forms that we fill ?

Thankfully, simple sorting and filtering solutions like implementing autocomplete in frontend technologies save us the effort and time and also introduces a layer input validation by abstracting away most of the form selection logic away from the user.

At its base...

Autocomplete is just about 3 things,

  1. Re-fetching the data from our API endpoint as the user keeps changing the characters in the input
  2. Filtering it as per the query typed
  3. Sorting the filtered results in a form most convenient to the user
  4. Re-rendering the UI to show the newly sorted, filtered results.

Let's start typing...

So right here, we are going to build a simple HTML input element using JSX and fill it dynamically with search results from the Pokemon API as the user keeps typing more characters

Obviously, you'll need a frontend technology to implement this. I am choosing React as I'm really comfortable with it but you can choose any technology or framework of your choice

Understanding the concept ....

Pretty simple, nothing to worry about. Remember, we just gotta do 4 things. Nice and smooth, right ?

The following table is an easy explainer on how this feature is implemented using simple state management

STATE ABOUT SETTER FUNCTION
search Stores the string that the user types in the input and which will be used for searching setSearch - Get's called whenever the input element is changed and causes a re-render (which leads to a re-render)
options Stores the array of items that the form suggests as the user types in the input element setOptions - Updates the options state with data based on the new search value when the component is re-rendered
display Nothing related to core autcomplete functionality. Just provides that UI feature where the options list is only visible when the use is actually typing in the input element



The following table is an easy explainer on how React hooks (basically just getter-setter functions of state) help us connect state management to UI re-rendering


HOOK FEATURE IMPLEMENTATION
useState State management Provides getter-setter functions for our options and search state
useRef DOM access Simply helps us access the DOM input element and change our search state based on the value typed in this element
useEffect Re-fetching and re-populating data Updates the options state on each re-render with data based on the new search value

The code implementation

Hardly a few paragraphs since the logic is just simple and pure

import React, { useEffect, useState, useRef } from "react";

const Auto = () => {
  const [display, setDisplay] = useState(false);
  const [options, setOptions] = useState([]);
  const [search, setSearch] = useState("");
  const wrapperRef = useRef(null);

  useEffect(() => {
    const pokemon = [];
    const promises = new Array(20)
      .fill()
    // Refetching
      .map((v, i) => fetch(`https://pokeapi.co/api/v2/pokemon-form/${i + 1}`));
    Promise.all(promises).then(pokemonArr => {
     // Filtering 
     return pokemonArr.map(value =>
        value
          .json()
          .then(({ name, sprites: { front_default: sprite } }) =>
            pokemon.push({ name, sprite })
          )
      );
    });
    setOptions(pokemon);
  }, []);

  // Simply closes the input when we click outside of it
  useEffect(() => {
    window.addEventListener("mousedown", handleClickOutside);
    return () => {
      window.removeEventListener("mousedown", handleClickOutside);
    };
  });

  const handleClickOutside = event => {
    const { current: wrap } = wrapperRef;
    if (wrap && !wrap.contains(event.target)) {
      setDisplay(false);
    }
  };

  const updatePokeDex = poke => {
    setSearch(poke);
    setDisplay(false);
  };

  return (
    <div ref={wrapperRef} className="flex-container flex-column pos-rel">
      <input
        id="auto"
        onClick={() => setDisplay(!display)}
        placeholder="Type to search"
        value={search}
        onChange={event => setSearch(event.target.value)}
      />
      {display && (
        <div className="autoContainer">
          {options
            .filter(({ name }) => name.indexOf(search.toLowerCase()) > -1)			
      		// Sorting
            .map((value, i) => {
              return (
                <div
                  onClick={() => updatePokeDex(value.name)}
                  className="option"
                  key={i}
                  tabIndex="0"
                >
                  <span>{value.name}</span>
                  <img src={value.sprite} alt="pokemon" />
                </div>
              );
            })}
        </div>
      )}
    </div>
  );
};

function App() {
  return (
    <div className="App">
      <h1>Custom AutoComplete React</h1>
      <div className="logo"></div>
      <div className="auto-container">
        <Auto />
      </div>
    </div>
  );
}

export default App;
4 steps - fetching, sorting, filtering, re-rendering

Conclusion

That's all there is, to this amazing functionality. You can now do interesting things with the fetching, sorting and filtering parts by integrating smart technologies like Elasticsearch to filter through large database at lightning speeds and pinpoint accuracy to show the most relevant results to the user.

Alternatively, you could also use something like Algolia, that provides an out-of-the-box integration of "smart searching" with "UI re-rendering" and just exposes simple to implement components that help you do this.

Either ways, you now have the power to create powerful search features with enhanced UI/UX for your frontend apps !