Building a React Autocomplete Search Bar

Building a React Autocomplete Search Bar

Breaking down how a filter and search bar component is built in React.

ยท

6 min read

This TinyApp, CitySearcher, focuses on a simple search bar component in React. I'll be breaking down how it works and how it's built. Let's get started. ๐Ÿš€

Meet CitySearcher

CitySearcher is an app that helps people pick a city that they're searching for from a pre-selected list of cities. You can play with the finished version of CitySearcher in the CodeSandbox below.

If you want to follow along, feel free to click on that "Open Sandbox" button to see the code. If not, I'll be pasting it in along the way.

App Component ๐ŸŒณ

Starting from the top, the App component is the root component in this application. It renders the background image and calls on the SearchBar component to be rendered.

export default function App() {
  return (
    <div className="App">
      <h1>CitySearcher</h1>
      <img src={building} alt="building" />
      <p>Search for a city below!</p>
      <SearchBar />
    </div>
  );
}

SearchBar.js ๐Ÿ•ต๏ธโ€โ™‚๏ธ

This is where the fun stuff happens. If you happened to glance at this file in the CodeSandbox, it might look a little scary, but let's demystify it.

First, let's set up state for the search bar input. This article assumes you know the basics of the useState React hook. If you don't, I highly recommend checking out the details in React's documentation first.

const [searchValue, setSearchValue] = useState("");

When we create our input, we'll set the value equal to searchValue. Every time someone types in it, the onChange event will fire. The onChange event fires a function that tells state to set the search value to that newly typed in value:

<input
  type="text"
  value={searchValue}
  onChange={(e) => setSearchValue(e.target.value)}
 />

Now we can type things into the input and state will change. Awesome sauce. Let's move on to the Autocomplete part.

Autocomplete ๐Ÿช„

Before digging right into the logic and code, let's get some context around what we're trying to achieve.

  1. We have an array of city names in cities.js
  2. When a user types into the search bar, they should see options that match the characters they've typed in.
  3. This should show up in a nice vertical list, so that people can see what cities we're searching for.

Filtering Cities ๐Ÿ™

First of all, let's import the array of cities into our SearchBar.js file with: import { cities } from "./cities";

Now let's leverage array methods in JavaScript to select values that match the user's current search value. The method we'll use here is the .filter method. This method accepts a function to select certain items in an array. Any item that returns a value of true will be selected.

Let's look at our example of this. We use .filter to fill up the matchingValues array:

const matchingValues = cities.filter((city) =>
    city.toLowerCase().includes(searchValue.toLowerCase())
  );

In English this code says: "Hey, I'm matchingValues, I've got some filtered values from the cities array. I check each city, and see if the city name (in lowercase) includes the search value (also lowercase). If so, I'll add that city to my array!"

Just incase the function speaking in first person was confusing, here's another way to put it. Any city value where city.toLowerCase().includes(searchValue.toLowerCase()) evaluates to true will be added to the matchingValues array.

This brings us to the .includes method in JavaScript. When called on a string, .includes checks if characters match in that string. If they do, it returns true.

With all the appropriately matching values in the matchingValues array, we can now render all of the filtered cities to the page.

Rendering Cities ๐ŸŒ†

To render the cities, we use the .map method. Found very frequently in React applications, .map will map over an array, and for each item in that array, it will return a value, added to a brand new array. Let's look at this in the context of our cities example.

The code we use to render the cities to the page is:

const citiesList = matchingValues.map((city) => {
    return <p key={city}>{city}</p>;
  });

We first take the matchingValues array, and when we call .map on it, we can handle each city individually. We then return a paragraph tag, with the city name inside it. Simply, we're looping over all the values that match our search term, and then, with .map, return an array of paragraph tags, ready to be used in our component.

When we map over an array and render multiple items in React, React needs a unique key value. In this example, we're setting the key equal to the city name, because in this TinyApp, the city names are all unique. In a larger production application, this would typically be an ID.

We can now easily add our list of cities to our component with {citiesList}.

But there's a problem with that...

Render cities only if there's a search term

This is a bit of a usability bonus. If you implemented a search bar based on the setup so far, it would be working! But there's an issue that makes it less of a joy to use. If the search bar is empty (like it would be when a user first gets to the page), the matchingValues array would have every single city in it, so every city would be displayed on the page, even though no one has searched for anything yet.

What would be far better is if the page was blank to start. Only when someone starts searching should it start filling up with city names. There are many ways to do this, but I chose to go with using what's called a ternary operator. I put it directly in the return of our component:

{searchValue.length > 0 ? citiesList : ""}

This line can look scary. I certainly was scared of this the first several times I saw it. Let's break it down.

There are three pieces to this line. The first is the expression. Then, there's what happens if the statement is true (after the ?), and what happens if the statement is false (after the :).

Remember, this statement is supposed to make sure that cities display if there's a value in the search box, and display nothing if there's no value in the search box.

The first part, searchValue.length > 0 is checking our searchValue state. It's saying, "Hey, is searchValue.length greater than 0?" AKA "Is the searchValue not empty?"

The ? indicates that we're checking whether that first expression is true or not. What follows the question mark is the second part, our citiesList. So far, if the searchValue has a length greater than 0 (it has something inside it), we tell React to render our list of cities.

The : is an indication that if the value of the expression is false, whatever comes next should be returned. In this case, it's an empty string: ""

Pulling it together, if searchValue.length > 0 is true, let's render all of the matching cities via our citiesList variable. If searchValue.length > 0 is false, let's render an empty string, which is basically nothing.

Instead of a ternary operator here, we totally could have used an if/else statement. But ternary operators are nice one-liners, and they get used quite a bit in the JavaScript universe.

We should now have a fully working, auto-completing search bar in React!

I hope you enjoyed this TinyApp! If you have questions, comments, or feedback, I'd love to hear them in the comments below. Thanks for reading!

~ Saalik

Further Reading

ย