Storing Data Locally for an Autocomplete/Typeahead Field
Aug 31, 2020 • 8 Minute Read
Introduction
Autocomplete fields are must-have user interface components that enhance the overall user experience of your app. They also help minimize redundant spelling mistakes when users search for specific terms. Since accessing client-side storage is incredibly fast, your app's performance will also improve.
In this guide, you will learn how to create an autocomplete field from scratch and store search suggestions locally in the browser.
Client-Side Storage
Client-side storage is one of the most useful and futuristic features of HTML5 on the browser. It helps shift the processing load from the server session to client-side variables.
Types of Client-side storage include:
- Session Storage
- Local Storage
- Web SQL
- Indexed DB
- File System
Local storage will be used for this guide, as it has a straightforward API compared to the other types of storage.
Using the localStorage API, you can access local storage for a particular domain or website. Data is stored in a key-value pair format in local storage.
The following code snippet accesses the local storage and adds a data item to it using the localStorage.setItem() method.
localStorage.setItem("Name", "John Doe");
The first argument is the name of the key, and the second argument is its corresponding value.
To retrieve an item from local storage, use the localStorage.getItem() method and pass the key as an argument.
localStorage.getItem("Name");
Getting Started With the Component
Now you can start working on the AutoComplete component. As you can see below, you need to have three properties in the state object. The inputValue property will hold the value of the text input, the filteredOptions property will hold the actual filtered result, and the options property will have all the available options that were previously typed in an array.
class AutoComplete extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
filteredOptions: [],
options: localStorage.getItem("autocompleteOptions")
? JSON.parse(localStorage.getItem("autocompleteOptions"))
: [],
};
// ...
}
render() {
// ...
}
}
Inside the render() method, display the input field and add the logic to show the results.
class AutoComplete extends Component {
constructor(props) {
// ...
}
render() {
return (
<div className="autocomplete-container">
<input
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
/>
<ul className="autocomplete-results">
{this.state.inputValue.length > 0 &&
this.state.filteredOptions.map((option) => (
<li key={option}>{option}</li>
))}
</ul>
</div>
);
}
}
In the handleInputChange() method, set the inputValue in the state and filter out the options using the Array.prototype.filter() method. To determine if the term entered in the input exists in the available options or not, you can use the indexOf method.
The indexOf() method returns the index or the position value within the calling String object of the first occurrence of the specified substring or value. It returns -1 if the value is not found in the string.
In the handleKeyDown() method, add the entered value to the list of available options when the user hits the enter key on the keyboard. The keyCode for the enter key is 13, so you can check if the keyCode of the entered value is equal to 13 inside the function body using the native event object.
class AutoComplete extends Component {
constructor(props) {
// ...
}
handleInputChange(e) {
const userInput = e.target.value;
this.setState({ inputValue: userInput });
const filteredOptions = this.state.options.filter(
(option) => option.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({ filteredOptions });
}
handleKeyDown(e) {
if (e.keyCode === 13) {
const userInput = e.target.value;
this.addOption(userInput);
this.setState({ inputValue: "" });
}
}
render() {
// ...
}
}
Storing Data In Local Storage
In the addOption() method, set the entered value in the options state and immediately store the updated options state in local storage. Don't forget to covert the array to string, as local storage does not support storing objects.
class AutoComplete extends Component {
constructor(props) {
// ...
}
addOption(option) {
this.setState({ options: [...this.state.options, option] }, () => {
localStorage.setItem(
"autocompleteOptions",
JSON.stringify(this.state.options)
);
});
}
// ...
render() {
// ...
}
Beautify The Component
All right, so now that the component is ready, add some styling to make it look pretty as no one likes to see the default browser styling. You can either copy the CSS code below or add the styling yourself.
.App {
font-family: sans-serif;
text-align: center;
}
.autocomplete-container {
width: 450px;
margin: auto;
}
.autocomplete-container input {
padding: 10px 18px;
font-size: 20px;
width: 100%;
border: 2px solid #999;
border-radius: 6px;
}
ul.autocomplete-results {
margin: 0;
margin-top: 8px;
list-style-type: none;
padding: 0;
}
ul.autocomplete-results li {
text-align: left;
padding: 10px 18px;
margin-bottom: 4px;
width: 100%;
box-shadow: 0px 3px 10px rgba(0, 0, 0, 0.08);
border: 1px solid #eee;
background: #fefefe;
}
Complete Code
Below you can find the complete code for the AutoComplete component for your reference.
import React, { Component } from "react";
class AutoComplete extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: "",
filteredOptions: [],
options: localStorage.getItem("autocompleteOptions")
? JSON.parse(localStorage.getItem("autocompleteOptions"))
: [],
};
this.addOption = this.addOption.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
addOption(option) {
this.setState({ options: [...this.state.options, option] }, () => {
localStorage.setItem(
"autocompleteOptions",
JSON.stringify(this.state.options)
);
});
}
handleInputChange(e) {
const userInput = e.target.value;
this.setState({ inputValue: userInput });
const filteredOptions = this.state.options.filter(
(option) => option.toLowerCase().indexOf(userInput.toLowerCase()) > -1
);
this.setState({ filteredOptions });
}
handleKeyDown(e) {
if (e.keyCode === 13) {
const userInput = e.target.value;
this.addOption(userInput);
this.setState({ inputValue: "" });
}
}
render() {
return (
<div className="autocomplete-container">
<input
type="text"
value={this.state.inputValue}
onChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
/>
<ul className="autocomplete-results">
{this.state.inputValue.length > 0 &&
this.state.filteredOptions.map((option) => (
<li
key={option}
onClick={() =>
this.setState({ inputValue: option, filteredOptions: [] })
}
>
{option}
</li>
))}
</ul>
</div>
);
}
}
export default AutoComplete;
Conclusion
So that's it from this guide. You have successfully managed to create the perfect AutoComplete component. You can go further with this component by combining server-side results with the client-side results using ajax calls. Proper state management and implementation using client-side storages can make your app amazingly fast and will give your users a much better user experience.