Github Repo
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, 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 since it gives us some additional features over the 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) component for displaying the results.
Lets start with our container, Search:
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 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, a database of music information. Grab an API key here.
We'll use axios to create a getInfo
method (check your API's docs on how to structure your request URL):
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:
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:
Home stretch. Now to render our results to the DOM.
As mentioned at setup let's create our presentational component, Suggestions
.
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:
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:
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!
Top comments (16)
Sir i got an error (Unhandled Rejection (TypeError): Cannot read property 'data' of undefined).
in this line (results:data.data).
I created my own API.
app.get('/Search',function(req,res){
con.query("select * from webtable where username like '%username%'",
function(err,searchrs){
console.log("Search results:",searchrs);
res.json(searchrs);
})
});
What corrections this API need ?
How to resolve that above error ?
Please answer me soon
Thanks in advance.
out need to check your output response because data is not declared the error says all
Cannot read property 'data' of undefined
Hey,
How can I get this to work with a Fetch API? I am using react-router and am able to type into the search bar and re-render with results that were fetched by "tags" which is a key in the json. For example, if I type the word "hip" in my search bar the page will re-render with videos that have the tag "hip" in their json.
However, I can't seem to connect this code you have here with what I am using and I think it is because I do not need to fetch from my navbar.
Thanks
Matt
Hey Matt - Do you have an example or sandbox I could look at? It should work pretty much identically with Fetch.
Hey Stefan, How and where can I use my API_KEY ?
Hi Fabricio,
You can define your API KEY in your shell instance by running the command:
$ export MGRPH_KEY=yourAPIkey
. Just make sure to start the app in the same window.You can also utilize a
.env
file.Lastly, you can add it as part of your
start
script for exampleHello,
musicgraph seems to be down and not responsive when testing API's. I was wondering is this the case for you as well.
sir how to hide that api list
There are a few ways you could hide the Suggestions component. I think a simple solution would be to create a state attribute called
showSuggestions
. Then you could show/hide it using React's short circuit syntax. In the render method add{ this.state.showSuggestions && <Suggestions results={this.state.results} /> }
. Then just usesetState
whenever you want to change the value ofshowSuggestions
, for example when the user leaves the input field.i hide the results but if i tried again in its shows no results
sir i am really confusing about setState Assynchronous function (its keep on calling Api even if setState is not change )
thank you sir
Using. . Env file
Musicgraph's site is no longer available. Use a different API if still looking at this in 2019.
have you tried this with multiple component like, in the table and on every row, you will put this component with suggestion in react.
Hi Ajie I haven't tried that personally but don't see why it wouldn't work.