How To Write a Search Component with Suggestions in React
Tags: react, api, javascript, beginners
Published: 2017-11-30 20:41:12

---
title: How To Write a Search Component with Suggestions in React
published: true
description: Using an external API to provide Search Suggestions
tags: React, API, JavaScript, beginners
---
[Github Repo](https://github.com/Sage911/blog-autocomplete)
_This example uses syntax that requires transpiling. See repo for full babel configuration._

Providing search suggestions is a great way to improve user experience. It can save time as well as guide users who are not exactly sure what they are looking for. 

With our 'why' identified we can move on to the implementation. But how do we implement suggestions in JavaScript? 

As is the case in most problem solving exercises, a good place to start is by asking the right questions:

* __Is there a library that solves what I'm trying to accomplish and should I use it?__ _A quick google search returns options like_ [autocomplete.js](http://autocomplete-js.com/), _but there is valuable insight to be gained by writing our own._

* __What HTML elements are we operating with?__ _Looks like we could use_ `<form>`, `<input/>`, `<ul>`.


###We've decided to write our own.

####What we'll need:

* A source of information. We're after a collection of values to compare against what our user has entered (we'll source an API response, but you could also use a local array of values).

* An HTTP client. Allows us to make requests to specific endpoints to GET the data we're looking for. I chose [axios](https://www.npmjs.com/package/axios) since it gives us some additional features over the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) such as automatic parsing of the data it receives.

* A smart/container component that makes API calls referencing a controlled input. 

* A presentational [(stateless functional)](https://reactjs.org/blog/2015/10/07/react-v0.14.html#stateless-functional-components) component for displaying the results.

Lets start with our container, Search:

```javascript
import React, { Component } from 'react'

class Search extends Component {
 state = {
   query: '',
 }

 handleInputChange = () => {
   this.setState({
     query: this.search.value
   })
 }

 render() {
   return (
     <form>
       <input
         placeholder="Search for..."
         ref={input => this.search = input}
         onChange={this.handleInputChange}
       />
       <p>{this.state.query}</p>
     </form>
   )
 }
}

export default Search

```
You'll notice that as you type into the input field, `Search` re-renders and our input's value is shown below. Thanks to [refs](https://reactjs.org/docs/refs-and-the-dom.html) we can select the input element and do useful things such as getting its value or invoking DOM events like `focus` (this.search.focus()).

Next, let's wire in an API. Here we'll use [MusicGraph](https://developer.musicgraph.com), a database of music information. Grab an API key [here](https://developer.musicgraph.com/signup?).

We'll use axios to create a `getInfo` method (check your API's docs on how to structure your request URL):

```javascript
  getInfo = () => {
    axios.get(`${API_URL}?api_key=${API_KEY}&prefix=${this.state.query}&limit=7`)
      .then(({ data }) => {
        this.setState({
          results: data.data
        })
      })
  }

```

Axios `.get` returns a promise meaning it does not block the rest of the application from executing while it waits for an API response. Here we are making a request to the MovieGraph API's artist endpoint, using the magic of refs to populate the prefix query parameter. (API_URL and API_KEY are being defined above the class definition, see next snapshot.)

Let's also tweak the `handleInputChange` method. We don't need to make an API call for every single onChange event, or when the input is cleared.

The full component so far:

```javascript
import React, { Component } from 'react'
import axios from 'axios'

const { API_KEY } = process.env
const API_URL = 'http://api.musicgraph.com/api/v2/artist/suggest'

class Search extends Component {
  state = {
    query: '',
    results: []
  }

  getInfo = () => {
    axios.get(`${API_URL}?api_key=${API_KEY}&prefix=${this.state.query}&limit=7`)
      .then(({ data }) => {
        this.setState({
          results: data.data // MusicGraph returns an object named data, 
                             // as does axios. So... data.data                             
        })
      })
  }

  handleInputChange = () => {
    this.setState({
      query: this.search.value
    }, () => {
      if (this.state.query && this.state.query.length > 1) {
        if (this.state.query.length % 2 === 0) {
          this.getInfo()
        }
      } 
    })
  }

  render() {
    return (
      <form>
        <input
          placeholder="Search for..."
          ref={input => this.search = input}
          onChange={this.handleInputChange}
        />
        <p>{this.state.query}</p>
      </form>
    )
  }
}

export default Search

```

If you have React Dev Tools installed in your browser you can watch the state of Search change as the API calls complete:
![React Dev Tools](https://res.cloudinary.com/dfcuz6b1o/image/upload/q_100/v1512061633/reactdevtoolssearch.png)

Home stretch. Now to render our results to the DOM.

As mentioned at setup let's create our presentational component, `Suggestions`.

```javascript
import React from 'react'

const Suggestions = (props) => {
  const options = props.results.map(r => (
    <li key={r.id}>
      {r.name}
    </li>
  ))
  return <ul>{options}</ul>
}

export default Suggestions
```

We've setup Suggestions to expect a prop named `results`. 

Let's render our Suggestions component:

```javascript
import React, { Component } from 'react'
import axios from 'axios'
import Suggestions from 'components/Suggestions'

const { API_KEY } = process.env
const API_URL = 'http://api.musicgraph.com/api/v2/artist/suggest'

class Search extends Component {
  state = {
    query: '',
    results: []
  }

  getInfo = () => {
    axios.get(`${API_URL}?api_key=${API_KEY}&prefix=${this.state.query}&limit=7`)
      .then(({ data }) => {
        this.setState({
          results: data.data
        })
      })
  }

  handleInputChange = () => {
    this.setState({
      query: this.search.value
    }, () => {
      if (this.state.query && this.state.query.length > 1) {
        if (this.state.query.length % 2 === 0) {
          this.getInfo()
        }
      } else if (!this.state.query) {
      }
    })
  }

  render() {
    return (
      <form>
        <input
          placeholder="Search for..."
          ref={input => this.search = input}
          onChange={this.handleInputChange}
        />
        <Suggestions results={this.state.results} />
      </form>
    )
  }
}

export default Search

```

Try it out:

![A working autocomplete](http://res.cloudinary.com/dfcuz6b1o/image/upload/q_100/v1512067865/autocomplete.png)

It works!

Once routing is setup, we can wrap each result in an anchor/react-router `Link` component. But that is a topic for another post. Until then, I hope this was helpful to someone!