How to create a synchronous and asynchronous autocomplete input in ReactJS

How to create a synchronous and asynchronous autocomplete input in ReactJS

As every user, I am usually lazy and don't want to type everything inside an input to search for something, at least knowing that when we can take advantage of the technology to make our life easier. That's why the autocomplete fields exists, the idea is simple, you type some text that may match with a listed option and you can select it, so you won't type wrong information about something as you can retrieve everything from your server or locally.

In this article we are going to show you how to implement an autocomplete component easily in your project to make the life of your users easy.

Requirements

To implement so quick and easy as possible an autocomplete component, we recommend you to use the react-autocomplete module. This module offers a WAI-ARIA compliant React autocomplete (combobox) component that can be easily customized according to your needs. To use it, proceed with the installation of the module in your project using the following command using NPM in your terminal:

npm install --save react-autocomplete

After the installation you will be able to import the components as Autocomplete from 'react-autocomplete'. For more information about this library, please visit the official repository at Github here.

A. Creating a synchronous autocomplete

If you are willing to use an autocomplete field that suggest local data (an already available array of objects in the class), you just need to define the data inside the state of the class and load it in the items prop of the Autocomplete component. The data will be in an array format, that is conformed by only objects. Every object is an item of the autocomplete list, the values can be named as you want (as you can define which properties of the object are rendered in the autocomplete). The following example implements 6 of the basic props of the component, we define a callback for the basic actions as when the input changes or the user selects some option of the autocomplete.

Besides, we customize the structure of every rendered item using the renderItem callback and declare an initial value, that in this case will be empty using the value property of the declared state:

import React from 'react';

// Import the Autocomplete Component
import Autocomplete from 'react-autocomplete';

export default class App extends React.Component {

    constructor(props, context) {
        super(props, context);

        // Set initial State
        this.state = {
            // Current value of the select field
            value: "",
            // Data that will be rendered in the autocomplete
            autocompleteData: [
                { 
                    label: 'Apple',
                    value: 1
                },
                { 
                    label: 'Microsoft',
                    value: 2
                },
                { 
                    label: 'Me, Myself and I',
                    value: 3
                }
            ]
        };

        // Bind `this` context to functions of the class
        this.onChange = this.onChange.bind(this);
        this.onSelect = this.onSelect.bind(this);
        this.getItemValue = this.getItemValue.bind(this);
        this.renderItem = this.renderItem.bind(this);
    }
    
    /**
     * Callback triggered when the user types in the autocomplete field
     * 
     * @param {Event} e JavaScript Event
     * @return {Event} Event of JavaScript can be used as usual.
     */
    onChange(e){
        this.setState({
            value: e.target.value
        });

        console.log("The Input Text has changed to ", e.target.value);
    }

    /**
     * Callback triggered when the autocomplete input changes.
     * 
     * @param {Object} val Value returned by the getItemValue function.
     * @return {Nothing} No value is returned
     */
    onSelect(val){
        this.setState({
            value: val
        });

        console.log("Option from 'database' selected : ", val);
    }

    /**
     * Define the markup of every rendered item of the autocomplete.
     * 
     * @param {Object} item Single object from the data that can be shown inside the autocomplete
     * @param {Boolean} isHighlighted declares wheter the item has been highlighted or not.
     * @return {Markup} Component
     */
    renderItem(item, isHighlighted){
        return (
            <div style={{ background: isHighlighted ? 'lightgray' : 'white' }}>
                {item.label}
            </div>   
        ); 
    }

    /**
     * Define which property of the autocomplete source will be show to the user.
     * 
     * @param {Object} item Single object from the data that can be shown inside the autocomplete
     * @return {String} val
     */
    getItemValue(item){
        // You can obviously only return the Label or the component you need to show
        // In this case we are going to show the value and the label that shows in the input
        // something like "1 - Microsoft"
        return `${item.value} - ${item.label}`;
    }

    render() {
        return (
            <div>
                <Autocomplete
                    getItemValue={this.getItemValue}
                    items={this.state.autocompleteData}
                    renderItem={this.renderItem}
                    value={this.state.value}
                    onChange={this.onChange}
                    onSelect={this.onSelect}
                />
            </div>
        );
    }
}

The previous class will render a very simple autocomplete that looks like:

Sync Autocomplete Example React

B. Creating an asynchronous autocomplete

Usually, an autocomplete implements an asynchronous interface too, that means that when the user types in the input, some logic runs in the background and retrieves some information from the server. Thanks to the React methodology and the component, this ain't so difficult to achieve as we use the state of React to do the hard job for us.

The process to implement the autocomplete will be the same as the synchronous one, however there's only one thing that changes, the data source. Initially, we set the autocompleteData to an empty array as we are supposed to load this information from our server. The logic to load the remote data into the autocomplete is simple, we just need to update the state of autocompleteData with the remote data and that's it ! Obviously the structure of the data needs to match with the declared in our logic locally (to obtain labels and values etc). In the following example, the function to retrieve the data from the remote source is retrieveDataAsynchronously, this function is triggered inside the onChange callback that need to be obviously declared. The function receives as first argument the current text of the input, that will be used by your server side logic to send some data according to the requested by the user:

import React from 'react';

// Import the Autocomplete Component
import Autocomplete from 'react-autocomplete';

export default class App extends React.Component {

    constructor(props, context) {
        super(props, context);

        // Set initial State
        this.state = {
            // Current value of the select field
            value: "",
            // Data that will be rendered in the autocomplete
            // As it is asynchronous, it is initially empty
            autocompleteData: []
        };

        // Bind `this` context to functions of the class
        this.onChange = this.onChange.bind(this);
        this.onSelect = this.onSelect.bind(this);
        this.getItemValue = this.getItemValue.bind(this);
        this.renderItem = this.renderItem.bind(this);
        this.retrieveDataAsynchronously = this.retrieveDataAsynchronously.bind(this);
    }


    /**
     * Updates the state of the autocomplete data with the remote data obtained via AJAX.
     * 
     * @param {String} searchText content of the input that will filter the autocomplete data.
     * @return {Nothing} The state is updated but no value is returned
     */
    retrieveDataAsynchronously(searchText){
        let _this = this;

        // Url of your website that process the data and returns a
        let url = `mywebsite/searchApi?query=${searchText}`;
        
        // Configure a basic AJAX request to your server side API
        // that returns the data according to the sent text
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'json';
        xhr.onload = () => {
            let status = xhr.status;

            if (status == 200) {
                // In this example we expects from the server data with the structure of:
                // [
                //    {
                //        label: "Some Text",
                //        value: 1,
                //    },
                //    {
                //        label: "Some Other Text",
                //        value: 1,
                //    },
                // ]
                // But you can obviously change the render data :)

                // Update the state with the remote data and that's it !
                _this.setState({
                    autocompleteData: xhr.response
                });

                // Show response of your server in the console
                console.log(xhr.response);
            } else {
                console.error("Cannot load data from remote source");
            }
        };

        xhr.send();
    }
    
    /**
     * Callback triggered when the user types in the autocomplete field
     * 
     * @param {Event} e JavaScript Event
     * @return {Event} Event of JavaScript can be used as usual.
     */
    onChange(e){
        this.setState({
            value: e.target.value
        });

        /**
         * Handle the remote request with the current text !
         */
        this.retrieveDataAsynchronously(e.target.value);

        console.log("The Input Text has changed to ", e.target.value);
    }

    /**
     * Callback triggered when the autocomplete input changes.
     * 
     * @param {Object} val Value returned by the getItemValue function.
     * @return {Nothing} No value is returned
     */
    onSelect(val){
        this.setState({
            value: val
        });

        console.log("Option from 'database' selected : ", val);
    }

    /**
     * Define the markup of every rendered item of the autocomplete.
     * 
     * @param {Object} item Single object from the data that can be shown inside the autocomplete
     * @param {Boolean} isHighlighted declares wheter the item has been highlighted or not.
     * @return {Markup} Component
     */
    renderItem(item, isHighlighted){
        return (
            <div style={{ background: isHighlighted ? 'lightgray' : 'white' }}>
                {item.label}
            </div>   
        ); 
    }

    /**
     * Define which property of the autocomplete source will be show to the user.
     * 
     * @param {Object} item Single object from the data that can be shown inside the autocomplete
     * @return {String} val
     */
    getItemValue(item){
        // You can obviously only return the Label or the component you need to show
        // In this case we are going to show the value and the label that shows in the input
        // something like "1 - Microsoft"
        return `${item.value} - ${item.label}`;
    }

    render() {
        return (
            <div>
                <Autocomplete
                    getItemValue={this.getItemValue}
                    items={this.state.autocompleteData}
                    renderItem={this.renderItem}
                    value={this.state.value}
                    onChange={this.onChange}
                    onSelect={this.onSelect}
                />
            </div>
        );
    }
}

When the user types on the autocomplete, the given text is sent to the server with an AJAX request and it should return as response an array with some data that matches with the providen text by the user. The state is updated with the data sent from the server and the user can pick an option. Note that the implementation here creates an ajax request that is executed everytime the user press a key, which means a lot of requests, so you may want to debounce the function someway. Remember that you can customize the input and the autocomplete items as you wish using just CSS by modifying the menuStyle prop.

Happy coding !

This could interest you

Become a more social person