Autocomplete component with React and Bootstrap

Implementing typeahead(autocomplete) functionality used to be quite challeging before the modern front end technologies were introduced.
In this tutorial, I will show you how I’ve implemented typeahead feature very quickly with React and Bootstrap.

Before we start

Aside from React, we will also use react-bootstrap which is a nice Bootstrap wrapper. Of course, you can use any styling library of choice, I choose Bootstrap due to its popularity and wide adoption.

npm install react-bootstrap bootstrap

Create the UI

First, we will focus on the UI part and in the next section we will make it dynamic.

In order the list of suggestion to be shown over the rest of the content, we need to add position:relative to the wrapping container(in our case the Form.Group component) and position:absolute to the list of suggesion element(ListGroup component). In this way we can position it precisely and place it over the rest of the content. You can read more about the use of CSS positioning here . We want the list to be displayed below the input field, so we assign it top value , which corresponds to the height of the input. All other styles applied to the elements are minor readjustments.

Below is the JSX for the typeahead:

export default function App() {
  return (
    <div className="App">
      <Form.Group className="typeahead-form-group">
        <Form.Control type="text" autoComplete="off" />
        <ListGroup className="typeahead-list-group">
          <ListGroup.Item className="typeahead-list-group-item">
            Example 1
          </ListGroup.Item>
          <ListGroup.Item className="typeahead-list-group-item">
            Example 2
          </ListGroup.Item>
          <ListGroup.Item className="typeahead-list-group-item">
            Example 3
          </ListGroup.Item>
        </ListGroup>
      </Form.Group>
    </div>
  );
}

And here is the CSS code:

.App {
  padding-top: 50px;
  font-family: sans-serif;
  text-align: center;
  width: 300px;
  margin: 0 auto;
}

.typeahead-form-group {
  position: relative;
}

.typeahead-list-group {
  position: absolute;
  width: 100%;
  top: 38px;
  left: 0;
}

.typeahead-list-group-item {
  padding: 0.3rem 1.3rem;
  background-color: #fff;
}

.typeahead-list-group-item:hover {
  cursor: pointer;
  background: #646464;
  color: #fff;
}

Mock API response

Now that the styling is in place, it’s time to focus on the data. We will create mock API response that simulates search results. We will filter through the data array and check if any of its children’s name property contains the given keyword. Of course, in real world application you will get the results from an API response and you won’t have to do this manually.

const data = [
  { id: 1, name: "devrecipes.net" },
  { id: 2, name: "devrecipes" },
  { id: 3, name: "devrecipe" },
  { id: 4, name: "dev recipes" },
  { id: 5, name: "development" }
];

const mockResults = (keyword) => {
  return new Promise((res, rej) => {
    setTimeout(() => {
      const searchResults = data.filter((item) => item.name.includes(keyword));
      res(searchResults);
    }, 500);
  });
};

Make it dynamic

Let’s first we need to introduce some the state that we will use in our component:

  const [results, setResults] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [name, setName] = useState("");
  const [isNameSelected, setIsNameSelected] = useState(false);

Where results is the array of results that we will display in the list, the isLoading property indicated whether we need to show a spinner and name is the value of the input. isNameSelected indicates where the name is selected from the autosuggest list.

Now it’s time to put in use the mock API that we’ve created. The first thing we have to do is to add onChange handler to the input field. Whenever its value is changed, we will get fresh results from the API that contain the keyword. If we don’t have any results, when we won’t show anything.

JSX for the input field:

  <Form.Control
    type="text"
    autoComplete="off"
    onChange={handleInputChange}
    value={name}
  />

And the handler:

  const handleInputChange = (e) => {
    const nameValue = e.target.value;
    setName(nameValue);
    // even if we've selected already an item from the list, we should reset it since it's been changed
    setIsNameSelected(false);
    // clean previous results, as would be the case if we get the                                      results from a server
    setResults([]);
    if (nameValue.length > 1) {
      setIsLoading(true);
      mockResults(nameValue)
        .then((res) => {
          setResults(res);
          setIsLoading(false);
        })
        .catch(() => {
          setIsLoading(false);
        });
    }
  };

The next step is to make the list of results dynamic and to improve the UX by adding loading spinner while the results are being fetched. I also add an onClick handler to each list iteam, ,so that whenever is clicked its value fills the input field.

JSX:

<ListGroup className="typeahead-list-group">
  {!isNameSelected &&
    results.length > 0 &&
    results.map((result) => (
      <ListGroup.Item
        key={result.id}
        className="typeahead-list-group-item"
        onClick={() => onNameSelected(result.name)}
      >
        {result.name}
      </ListGroup.Item>
    ))}
  {!results.length && isLoading && (
    <div className="typeahead-spinner-container">
      <Spinner animation="border" />
    </div>
  )}
</ListGroup>

And the handler for clicking on a list item:

const onNameSelected = (selectedName) => {
  setName(selectedName);
  setResults([]);
};

Below is the end result, you can try it out by typing slowly devrecipes.net.

Now what?

This is a very basic version of typeahead. You can use this example and extend it fo fit your needs. In real world application, it’s a good practice to use throttle to avoid dozens of unnecesary requests, especially when user is typing quickly.

If you find this tutorial useful, don’t hesitate to share it and check out other materials in my blog.