Project 49 of 100 - Search with Autocomplete
Tags: react, javascript, 100daysofcode
Published: 2021-02-21 06:20:38

*Hey! I'm on a mission to make 100 React.js projects ending March 31st. Please follow my dev.to profile or my [twitter](https://www.twitter.com/jwhubert91) for updates and feel free to reach out if you have questions. Thanks for your support!*

Link to the deployed project: [Link](https://100-react-projects-day-49-autocomplete.netlify.app/)
Link to the repo: [github](https://github.com/jwhubert91/100daysofreact/tree/master/day-49-autocomplete-start-wars)

Today I wanted to create an autocomplete component in React because I haven't implemented search in React before much less autocomplete. I'm actually curious how other people implement this from scratch because with the Star Wars API it's fairly easy. They have their own search feature that returns an array of JSON results and the number of Star Wars characters any search is going to return is necessarily small. What if your database has 100,000 possible results? I suppose you could put a numeric limit on the results in most databases.

For the basic search component I adapted [this Dev.to blog post](https://dev.to/sage911/how-to-write-a-search-component-with-suggestions-in-react-d20) into a functional component. Rather than use their API, I decided to use [a Star Wars character search API](https://swapi.dev/) that is open and doesn't require a signup that exposes my email.

The structure of the website is simple. It uses an `App` component and a `Search` component, where the important logic happens. It uses three pieces of state- `query`, `searchResults` and `selectedCharacter` which are all set to empty at initialization:

```
  const [query,setQuery] = useState('');
  const [searchResults,setSearchResults] = useState([]);
  const [selectedCharacter,setSelectedCharacter] = useState(null);
```

In the return statement we create a form with a text input for the search feature:

```
return (
    <form className='search-form-container'>
      <input 
        placeholder='Search for...'
        onChange={handleInputChange}
        value={query}
      />
    </form>
  )
```

As the user searches we initiate the API call to the Star Wars API using their search URL query:

```
const searchURL = 'https://swapi.dev/api/people/?search=';

const getInfo = () => {
    console.log('Getting info from API...')
    fetch(searchURL+query)
      .then(res => res.json())
      .then(data => setSearchResults(data.results))
      .catch(e => {
        console.log({error: e});
    });
}

const handleInputChange = (e) => {
    setQuery(e.target.value)
    if (query && query.length > 0) {
      getInfo();
    }
}
```

If results are returned from the API, we populate a `ul` element beneath the search box with results. I usually use the standard map method and create a `key` prop for the returned JSX children but I wanted to implement this a new way- with the `React.Children.toArray()` method. This way, you don't have to create your own key prop.

```
const results = React.Children.toArray(
    searchResults.map((item,idx) => (
      <li className='result-item' id={idx} onClick={handleQueryResultClick}>{item.name}</li>
    ))
)
```

That looks like the following:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1rz0c3k8iku1x2gc8ap.png)

If the user selects one of these `li` elements, the index of that element from the original array of results stored in `searchResults` will match up with the id of the `li` element.

```
const handleQueryResultClick = (e) => {
    const searchResultId = e.target.id;
    setSelectedCharacter(searchResults[searchResultId]);
    setQuery([]);
}
```

We then populate select data from that character's search into a div below the search box and clear the query state to remove the `ul` element of search results. I did this with a ternary.

```
<div>
        {selectedCharacter ? (
          <div className='character-display-container'>
            <p><span className='character-info-title'>name:</span> {selectedCharacter.name}</p>
            <p><span className='character-info-title'>height:</span> {selectedCharacter.height}</p>
            <p><span className='character-info-title'>mass:</span> {selectedCharacter.mass}</p>
          </div>
        ) : (
          <p className='no-results-prompt'>There are no results. Try typing something into the search bar above.</p>
        )}
</div>
```

That's it! It was easier than I expected, largely because the API is so easy to use. I highly encourage you to try it.