Learn how to create autocomplete search functionality in react.
We will create a search functionality which will show the suggestion when something is typed such as an autocomplete feature.
Just like how when we type something type in google and it shows suggestion. We will create the UI aspect of the autocomplete not the functional part.
We will use a dummy api to fetch and show the data.
This is a little complex component because we need to handle many things. I will try to break it down and make it as simple as possible.
So grab your seat and get ready to learn something amazing 😁😎.
Search autocomplete in react.
Our component is composed of three different parts.
- Search box which will get the input.
- Store the state of the component and fetch the data when something is typed.
- Show the fetched data in the suggestion display area.
We will also use some advance techniques which will help us in performance optimization.
There are few extra packages that we will be using for our help in development.
'prop-types':- It helps to validate the props that we will receive.'classnames':- Using this we can use CSS classes as javascript object, we just need to name our CSS file asfilename.module.css.'lodash':- A set of helper functions to fasten the development.
Following is the folder structure of our component.

Now as you got some good idea of our app, let us begin creating it.
First, import all the required packages at the top.
import React, { Component } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import { debounce } from "lodash";
import styles from "./index.module.css";
const ITEMS_API_URL = "https://demo.dataverse.org/api/search";
const DEBOUNCE_DELAY = 500;
class AutoComplete extends Component {
//Other code will go here
}
export default AutoComplete;
This is not a controlled component it is independent but it will return the changes to the parent. So we will be expecting two props from the parent.
One, which return the list of suggestions and second will return the item which is clicked.
So validate the props at the beginning to make sure we receive proper things.
static propTypes = {
onChange: PropTypes.func,
onSelectItem: PropTypes.func
};
Now setup the state to track the changes of the component.
constructor(props) {
super(props);
this.state = {
inputStr: "",
items: [],
isLoading: false,
error: {
status: false,
message: {}
}
};
}
As we need to track multiple things, we have created a list for it.
inputStrwill store the things we have typed in search box.itemswill store the result from the API.isLoadingto track the state.errorto keep the error message.
Now lets create the layout of the component.
render() {
const { items, isLoading } = this.state;
//Generate the list of returned suggestions
const list = items.map(e => (
<span
key={e.name}
onClick={() => this.onItemClick(e)}
className={styles.listItem}
>
{e.name}
</span>
));
//Can we show the suggestions
const canShow = list.length > 0 && !isLoading;
return (
<div className={styles.wrapper}>
{/* Add loading state to the search area */}
<div
className={cx(styles.control, {
[styles.isLoading]: isLoading
})}
>
{/* Search box */}
<input
type="search"
className={styles.searchBox}
onChange={this.onInputChangeHandler}
/>
</div>
{/* Show the suggestion list*/}
{canShow && (
<div className={cx(styles.displayArea, styles.isHoverable)}>
{list}
</div>
)}
</div>
);
}
As you can see we are generating list of items from the list and displaying the result only when it is available.
Let us create the event listener for the search box which will help us to store search string.
onInputChangeHandler = e => {
const { value } = e.target;
this.setState({
inputStr: value
});
//Call the api
this.delayedCallback();
};
After saving the query string we are calling the function which will make the API call and store the data.
But If we keep making api call as user keeps typing it will really impact the performance.
That is why we will be using an optimization method call debounce of calling a function.
This will only call the function after user stops typing for a certain amount of time. This will help us to make sure that we don’t keep making unnecessary API calls.
As I have already mentioned we will be using some advance technique this what I was talking about.
For this we will have to wrap our API calling function inside the debounce. lodash already has debounce so we will be using this. I had already imported this on the top.
Do it inside the constructor so that we don’t mess it around.
constructor(props){
super(props);
//State
this.delayedCallback = debounce(this.fetchItems, DEBOUNCE_DELAY);
}
Now lets create our function to call the API.
fetchItems = async () => {
const { inputStr } = this.state;
const { onChange } = this.props;
this.setState({
isLoading: true
});
try {
//Update the state
const res = await fetch(`${ITEMS_API_URL}?q=${inputStr}`);
const json = await res.json();
const { data } = json;
const { items } = data;
this.setState({
items
});
//Return the data to the parent
onChange && onChange(items);
} catch (e) {
this.setState({
error: {
status: true,
message: e
}
});
} finally {
this.setState({
isLoading: false
});
}
};
Before making the API call, set the isLoading to true then if the data is fetched successfully set the items list and return the data to the parent by calling the function we have received in the prop.
If there is error then update the error message and finally irrespective of what happens update the isLoading to false.
The last thing pending is notifying the parent about which item in the list is clicked. As we have already assigned the function for the click event on the items list, lets just define it.
onItemClick(e) {
const { onSelectItem } = this.props;
//Notify the parent
onSelectItem && onSelectItem(e);
}
Complete code of search autocomplete in react.
import React, { Component } from "react";
import cx from "classnames";
import PropTypes from "prop-types";
import { debounce } from "lodash";
import styles from "./index.module.css";
const ITEMS_API_URL = "https://demo.dataverse.org/api/search";
const DEBOUNCE_DELAY = 500;
class AutoComplete extends Component {
static propTypes = {
onChange: PropTypes.func,
onSelectItem: PropTypes.func
};
constructor(props) {
super(props);
this.state = {
inputStr: "",
items: [],
isLoading: false,
error: {
status: false,
message: {}
}
};
this.delayedCallback = debounce(this.fetchItems, DEBOUNCE_DELAY);
}
onInputChangeHandler = e => {
const { value } = e.target;
this.setState({
inputStr: value
});
this.delayedCallback();
};
fetchItems = async () => {
const { inputStr } = this.state;
const { onChange } = this.props;
this.setState({
isLoading: true
});
try {
//Update the state
const res = await fetch(`${ITEMS_API_URL}?q=${inputStr}`);
const json = await res.json();
const { data } = json;
const { items } = data;
this.setState({
items
});
//Return the data to the parent
onChange && onChange(items);
} catch (e) {
this.setState({
error: {
status: true,
message: e
}
});
} finally {
this.setState({
isLoading: false
});
}
};
onItemClick(e) {
const { onSelectItem } = this.props;
//Notify the parent
onSelectItem && onSelectItem(e);
}
render() {
const { items, isLoading } = this.state;
//Generate the list of returned suggestions
const list = items.map(e => (
<span
key={e.name}
onClick={() => this.onItemClick(e)}
className={styles.listItem}
>
{e.name}
</span>
));
//Can we show the suggestions
const canShow = list.length > 0 && !isLoading;
return (
<div className={styles.wrapper}>
{/* Add loading state to the search area */}
<div
className={cx(styles.control, {
[styles.isLoading]: isLoading
})}
>
{/* Search box */}
<input
type="search"
className={styles.searchBox}
onChange={this.onInputChangeHandler}
/>
</div>
{/* Show the suggestion list*/}
{canShow && (
<div className={cx(styles.displayArea, styles.isHoverable)}>
{list}
</div>
)}
</div>
);
}
}
export default AutoComplete;
It was really amazing doing this, lets now design it.
Complete style code of our component.
We will keep the design really simple.
//index.module.css
.wrapper {
display: inline-flex;
position: relative;
flex-direction: column;
}
.searchBox {
padding: 10px;
border: 1px solid #000;
border-radius: 5px;
font-size: 14px;
color: #607d8b;
cursor: pointer;
}
.displayArea {
position: relative;
left: 1px;
top: 8px;
max-width: 200px;
min-height: 100px;
background: #f5f0f0;
box-shadow: rgba(0, 0, 0, 0.4) 0 1px 3px;
padding: 5px;
}
.listItem {
padding: 5px 0;
display: block;
border-bottom: 1px solid #37474f;
color: #ff9800;
}
.listItem:last-child {
border: none;
}
Input
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import AutoComplete from "./Components/Search";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<div className="abc">
<AutoComplete />
</div>,
document.getElementById("root")
);
Output
