Adding Dynamic Search Auto-Complete to a web application with NodeJS and React 🔍
Tags: react, javascript, node, webdev
Published: 2022-02-09 18:18:04

## Introduction
You must've seen google's autocomplete search bar predict your next keystrokes as you type in it

![google autocomplete search feature](https://cdn.hashnode.com/res/hashnode/image/upload/v1644399835023/gYyIB_V1D.png)

So, I thought of building one and sharing my findings with y'all.
Let's get started 🚀

## Google's search auto-complete API
I was making an app that required me to implement this sort of thing where a user starts typing and some search suggestions would come flying in his face (in a div of course). So I started to wander around on google for some free APIs. In the quest, I stumbled upon [this](https://stackoverflow.com/questions/6428502/google-search-autocomplete-api) stack overflow discussion. One of the answers suggested to use:
```HTML
https://www.google.com/complete/search?client=hp&hl=en&sugexp=msedr&gs_rn=62&gs_ri=hp&cp=1&gs_id=9c&q=a&xhr=t&callback=hello
```
This basically is the URL that google.com itself uses for its search suggestions

![google.com search suggestions page with inspector opened at network tab](https://cdn.hashnode.com/res/hashnode/image/upload/v1644401507572/Vkmsl5cg4.png)

## API Endpoint
Let's now get on writing some GET requests to google's API from a test browser environment, I find codesandbox.io the best way to set up a quick hosted web environment for such things.

![code sandbox code to fetch google auto-complete search API](https://cdn.hashnode.com/res/hashnode/image/upload/v1644403982454/ex9g4AOXr.png)

And.....refresh!

![access control origin error from browser environment](https://cdn.hashnode.com/res/hashnode/image/upload/v1644405130693/l1iOst4oJ.png)

Hmm, so this is a CORS error, relatively easy to fix only if you know how to work with NodeJS ;)

### "What just happened?"
These errors happen when a header `Access-Control-Allow-Origin` is not present. Responses from requests that don't have this header are blocked by the browser even if the request returns 200 OK.

>The Access-Control-Allow-Origin response header indicates whether the response can be shared with requesting code from the given origin.

Find more on this header [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin). This error is only raised in a browser environment. Therefore, now we will be switching this request-making code block to a node environment and then return the response to the requesting client for our node API endpoint.

Also, if we just make a GET request with our local machine, like on postman or insomnia. It will be returning a normal response for our specified url-encoded query `q`

![200 OK insomnia response for google auto-complete search api](https://cdn.hashnode.com/res/hashnode/image/upload/v1644406430803/sv2loI5zw.png)

Now, let's code our node endpoint to get the search suggestions and return them as a JSON response.

![replit nodejs app to fetch and return autocomplete search results as json](https://cdn.hashnode.com/res/hashnode/image/upload/v1644409345797/u6g9FE2_M.png)

For quick code snippets instantly deployed in a hosted environment, I often like to use replit.com.
In this code, I imported `express` (for spinning up a web server) and `axios` (for making GET/POST requests to sources over the internet).

Then, I initialized the express server/app and defined an API GET ENDPOINT with the `.get(url, (request, response) => {})` method, made a GET request with `axios` to google's API and sent the returned suggestions data to the requesting client.

Testing the newly made endpoint, will obviously return a 200 OK response and provide us with the search results

![I am the best in nodejs](https://cdn.hashnode.com/res/hashnode/image/upload/v1644410404950/PD9-ckDri.png)

![](https://media.giphy.com/media/3o6ZteV7P19i45OXsc/giphy.gif)

But there is 1 more task remaining with our endpoint, returning nice & sweetly formatted search suggestions in the JSON format

![code to format returned data](https://cdn.hashnode.com/res/hashnode/image/upload/v1644427967478/4ObekJBrP.png)

For this part of the code, I referenced the main part of the result with some indexing, used regular expressions to grab just the part where it gives the actual completion text, and add some null checks for the regex matches. Now our API response looks something like this

![nice and sweet response](https://cdn.hashnode.com/res/hashnode/image/upload/v1644411449603/Gemq_FtEv.png)

## Implementing the API in a React application
So, now comes the part where we actually use our API to dynamically suggest users for their search keystrokes. On code sandbox, let's spin up a basic react app that takes input & saves it in its state

![basic react app with simple input state](https://cdn.hashnode.com/res/hashnode/image/upload/v1644413216117/akUlff4Dp.png)

Now, we don't want to spam our endpoint with dozens of requests on every keystroke whenever the input value is changed. So, we'll be implementing a timeout-based request fetching, something like this

```javascript
import axios from "axios";
import { useState } from "react";

export default function App() {
  const [searchResults, setSearchResults] = useState([]);
  const [inputText, setInputText] = useState("");
  const [inputTimer, setInputTimer] = useState(null);

  const handleInputChange = async (e) => {
    setInputText(e.target.value);

    clearTimeout(inputTimer);
    let timeout = setTimeout(() => {
      console.log("FETCHING RESULTS");
      axios
        .get(
          `https://autocomplete-google-search.kuvambhardwaj.repl.co/autocomplete?q=${e.target.value}`
        )
        .then((res) => {
          setSearchResults(res.data);
        });
    }, 300);
    setInputTimer(timeout);
  };

  return (
    <div className="App">
      <center>
        <input
          value={inputText}
          onChange={handleInputChange}
          placeholder="Type something"
          style={{ fontSize: "24px" }}
        />

        <div style={{ marginTop: "30px" }}>
          <ul>
            {searchResults.map((searchResult) => (
              <li>{searchResult}</li>
            ))}
          </ul>
        </div>
      </center>
    </div>
  )
}
```

So, now what we're doing is set a timeout for like 300ms whenever the input value changes & store the timeout reference in the state, if the user enters a letter and the input value is changed within 300ms, it will clear the previously set timeout and initialize a new one. After 300ms of input inactivity, we will be finally making autocomplete request with the most recent value of the input.
The application should work something like this

![working thingy](https://media.giphy.com/media/UuA0CJeNfnJ2kcbbS5/giphy.gif)

![](https://media.giphy.com/media/26xBE8LbggMqwH1PW/giphy.gif)

-----------------
If you like my posts, consider following :)

Twitter -> [@BhardwajKuvam](https://twitter.com/BhardwajKuvam)<br>
Github -> [@kuvamdazeus](https://github.com/kuvamdazeus)<br>
LinkedIn -> [@kuvambhardwaj](https://www.linkedin.com/in/kuvambhardwaj)<br>
Portfolio -> [kuvambhardwaj.vercel.app](https://kuvambhardwaj.vercel.app)


[<img src='https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lokbm1o1fdnkevsvr7m.png' height='40px' />](https://www.buymeacoffee.com/kuvam)