# Tutorial: Learn how to create a spotlight like search experience with autocomplete.js and appbase.io

In this post we are going to build a Spotlight like search experience using React and [Elasticsearch](https://www.appbase.io/what-is/elasticsearch): a searchbar which can not only search for results but also navigate to links and perform custom tasks. This lets you create a swiss army knife with search as the interface. Whether it is showing search results from your app, docs, external sources, or navigating to menu items or pages, or running an action like switching the app theme or sending out an e-mail, you can do it all!

You can check out the example app we will be building in this post over here.

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/master/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="saas-search"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

# Beyond searchbox
There are a lot of things we can do improve UX. Making our searchbox go beyond just searching for index results is one of them.

New users not familiar with the interface can get lost and not find features quickly. Help them by enabling discovery of features through search.

Users hate using the mouse. Eliminate the need to use the mouse by getting more done through the keyboard. Navigate pages without even having to click on a link.

If your site has a billion features then your users might not find the correct feature when they need it. Solve the problem of feature discovery through search.

# Things needed

We would be building this app using [autocomplete.js](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/) and [autocomplete-suggestions-plugin](https://docs.appbase.io/docs/reactivesearch/autocomplete-plugin/quickstart/).

### Autocomplete UI

The suggestions which appear while typing, has become a much needed feature for search. It can minimize typing and enable users to search quicker. To build an interactive and accessible autocomplete UI we use [autocomplete.js](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/).

### Getting data for autocomplete

Building autocomplete UI is just one part of the equation. We need data for our suggestions. We will be using a dataset of songs. We will need an Elasticsearch index to host our data. In this post, we will use [reactivesearch.io](https://www.reactivesearch.io/product/search) for doing this but you can also run your own Elasticsearch cluster and create an index. You can also view it in [dejavu data browser](https://dejavu.appbase.io/?appname=best-buy-dataset&url=https://b8917d239a52:82a2f609-6439-4253-a542-3697f5545947@appbase-demo-ansible-abxiydt-arc.searchbase.io&mode=view). We can use below details of the index for configuring the app.

```json
{
  "url": "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
  "app": "best-buy-dataset",
  "credentials": "b8917d239a52:82a2f609-6439-4253-a542-3697f5545947"
}
```

In order to use the data, we use [autocomplete-suggestion-plugin](https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/) from [ReactiveSearch](https://opensource.appbase.io/reactivesearch/). Reactivesearch has a wide range of suggestions. It supports popular suggestions based on the trending user searches. Not only that, it supports for recent suggestions, that the current user has last searched. Explore [Reactivesearch](https://opensource.appbase.io/reactivesearch/) for learning more.

# Building the app

### Step 1: Setup

We are going to use [CRA](https://create-react-app.dev/) for setting up the project. This generates a scaffolding for getting started to use React in a project. Run the below command from a CLI.

```sh
npm install -g create-react-app  # install CRA if you don't have it.
npx create-react-app saas-search      # initialize the boilerplate.
cd saas-search
```

The folder structure would something like below.

```
saas-search
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── package-lock.json
├── public
│   └── favicon.ico
│   └── index.html
│   └── manifest.json
└── src
    └── App.css
    └── App.js
    └── App.test.js
    └── index.css
    └── index.js
    └── logo.svg
    └── registerServiceWorker.js
```

We have removed some unnecessary files and folders. You can check the [resulting setup.](https://github.com/appbaseio-apps/saas-search/tree/step-1-cra)

### Step 2: Adding a searchbar

Make a file in `src` named `Autocomplete.js`.

We are going to make a autocomplete searchbar using [autocomplete.js](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/). Because autocomplete makes changes to the DOM which React is unaware, we have to use an [escape-hatch](https://reactjs.org/docs/integrating-with-other-libraries.html) that react provides `useRef`. We get a reference to a dom node using `useRef` and then pass it to autocomplete. Autocomplete also does some cleanup by calling `search.destroy()`.

```js
//Autocomplete.js
export function Autocomplete(props) {
  const containerRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) {
      return undefined;
    }

    // Initialize autocomplete on the newly created container
    const search = autocomplete({
      container: containerRef.current,
      ...props,
    });
    // Destroy the search instance in cleanup
    return () => {
      search.destroy();
    };
  }, [props]);

  return <div ref={containerRef} />;
}
```

We can then use the `Autocomplete` searchbar component inside of our app.

```js
//index.js
function App() {
  return (
    <div className="App">
      <Autocomplete openOnFocus={true} defaultActiveItemId={0} />
    </div>
  );
}
```
<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-2-add-searchbar/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

### Step 3: Theme and styles

Our app doesn't look exciting right now. Let's add some styling and theme to it. We would use a [theme](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic/) from algolia. We would `import` this theme in `Autocomplete.js`

```js
//Autocomplete.js
import "@algolia/autocomplete-theme-classic";
```

We can add some custom styles to configure fonts, colors in [`App.css`](https://github.com/appbaseio-apps/saas-search/). The app should look better.

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-3-custom-styles/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


### Step 4: Showing suggestions

Our app looks good by now, but we don't get any suggestions when we type. In this step we would be creating and configuring four plugins. The first two would be created completely from scratch: `githubRepoPlugin` and `functionsPlugin`. The other two we would create using [autocomplete-suggestions-plugin](https://docs.appbase.io/docs/reactivesearch/autocomplete-plugin/quickstart/).

This plugin renders repository links as suggestions. This is a custom plugin created using autocomplete. We have a static array as a data source. Each item contains a `label` for suggestions and `description`. Each item also contains information about whether to open the link in a new tab or same tab using `target` attribute.

```js
//githubReposPlugin.js
const data = [
  {
    label: "@appbaseio/autocomplete-suggestions-plugin (Same tab)",
    description:
      "An appbase.io suggestions plugin for autocomplete.js library from Algolia",
    link: {
      target: "_self",
      url: "https://github.com/appbaseio/autocomplete-suggestions-plugin",
    },
  },
];
```

The plugin is created as below. [`<Suggestion/>`](https:/github.com/appbaseio-apps/saas-search/tree/master/src/plugins/Suggestion.js) is a UI component for rendering suggestions. We use the `getSources` which is used by **autocomplete.js** to get data for suggestions. We match the `query`(the search term typed by user) against the `label` in `getItems` method. The `templates` property is used to create custom UI elements for styling. We use `item` for rendering custom suggestion, but there are many more, whose usage is [shown in documentation.](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates/). We also have a event handler `onSelect` which gets called when an `item` is clicked by the user.

```js
//githubReposPlugin.js
const gitHubReposPlugin = {
  getSources({ query }) {
    return [
      {
        sourceId: "github-repos",
        getItems() {
          return data.filter(({ label }) =>
            label.toLowerCase().includes(query.toLowerCase())
          );
        },
        templates: {
          item({ item }) {
            return <Suggestion item={item} />;
          },
          noResults() {
            return "No results";
          },
        },
        onSelect({ item }) {
          if (item.link.url) window.open(item.link.url, item.link.target);
        },
      },
    ];
  },
};
```

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-4-github-repo-plugin/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

### Step 5: Showing functions as suggestions

We get suggestions, but it is still a normal searchbar. We want to search for functions and perform actions. We would be making a plugin for this as well.

The data for our app would look as below. The structure is same as the `githubReposPlugin` but we have two new properties: `callback` and `matchAny`. `callback` is used when the suggestion is clicked. It gets passed `item` as argument. `matchAny` tells whether to match any query searched by user.

```js
const data = [
  {
    label: "/alert",
    description: "Throws a friendly alert",
    callback: (item) => alert(`An alert was triggered.`),
    icon: bellIcon,
    matchAny: true,
  },
];
```

The code for plugin is as below. It is similar to `githubReposPlugin`. We add a `header` to label this group of suggestions.
```js
//functionsPlugin.js
const functionsPlugin = {
  getSources({ query }) {
    return [
      {
        sourceId: "functions",
        getItems() {
          return data.filter(({ label }) =>
            label.toLowerCase().includes(query.toLowerCase())
          );
        },
        templates: {
          header() {
            return <div className="header">Functions</div>;
          },
          item({ item }) {
            return <Suggestion item={item} icon={item.icon} />;
          },
          noResults() {
            return "No results";
          },
        },
        onSelect({ item }) {
          if (typeof item.callback === "function") item.callback(item);
        },
      },
    ];
  },
};
```


**Dark mode function:**
Let's add more functions in suggestions. We would like to toggle theme of our website using our keyboard. We would add this function in data of `functionsPlugin.js`

```js
//functionsPlugin.js
const data = [
  {
    label: "/dark",
    description: "Enable dark mode",
    callback: () => {
      const bodyElement = document.querySelector("body");
      bodyElement.classList.add("dark");
    },
    icon: moonIcon,
    matchAny: false,
  },
  {
    label: "/light",
    description: "Enable light mode",
    callback: () => {
      const bodyElement = document.querySelector("body");
      bodyElement.classList.remove("dark");
    },
    icon: sunIcon,
    matchAny: false,
  }
]
```

**Google search function:**
We would now add a function to similar to "Search on google" context menu item. This would take the search query and open in a new tab a google search.

```js
//functionPlugin.js
const data = [
  {
    label: "Search on Google",
    description: "Google search in new window",
    callback: (query) => {
      const queryParam = query.replace(" ", "+");
      //Open in new window
      window.open(`https://www.google.com/search?q=${queryParam}`, "_blank");
    },
    icon: googleIcon,
    matchAny: true,
  },
]
```

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-5-google-search/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


### Step 6: Showing suggestions from our index
Our SAAS-searchbar is looking good and can do various things. But we also want it to show suggestions from our index. For that we would be using [autocomplete-suggestions-plugin](https://docs.appbase.io/docs/reactivesearch/autocomplete-plugin/quickstart/).

**Configuration:**

First we make some config variables to use in the plugin. This details are available from your [appbase cluster.](https://docs.appbase.io/docs/reactivesearch/autocomplete-plugin/apireference/)

Below is appbase config with app(index), url and credentials. The `settings` field is optional.

```js
//config.js
// appbase client config object
export const appbaseClientConfig = {
  url: "https://appbase-demo-ansible-abxiydt-arc.searchbase.io",
  app: "best-buy-dataset",
  credentials: "b8917d239a52:82a2f609-6439-4253-a542-3697f5545947",
  settings: {
    userId: "s@s",
    enableQueryRules: true,
    recordAnalytics: true,
  },
};
```

Below is **reactivesearch** config. It is similar to `props` you would pass to [`SearchBox` component.](https://docs.appbase.io/docs/reactivesearch/v3/search/searchbox/#props)

```js
//config.js
export const rsApiConfig = {
  highlight: true,
  dataField: [
    {
      field: "name.autosuggest",
      weight: "1",
    },
    {
      field: "name",
      weight: "3",
    },
  ],
  enableRecentSuggestions: true,
  recentSuggestionsConfig: {
    size: 2,
    minHits: 2,
    minChars: 4,
    index: "best-buy-dataset",
  },
  enablePopularSuggestions: true,
  popularSuggestionsConfig: {
    size: 2,
    minChars: 3,
    minCount: 3,
    index: "best-buy-dataset",
  },
  index: "best-buy-dataset",
  size: 5,
};
```

**Default usage:**

In order to fetch results from **best-buy-dataset** index, we need to create a plugin using [**autocomplete-suggestions-plugin**.](https://github.com/appbaseio/autocomplete-suggestions-plugin)
We can pass the variables created above to the plugin and we are done.

```js
import createSuggestionsPlugin from "@appbaseio/autocomplete-suggestions-plugin";

const defaultUsagePlugin = createSuggestionPlugin(
  appbaseClientConfig,
  rsApiConfig
);
```

Then you can just pass it inside [`autocomplete.js`.](https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-js/autocomplete/).

```js
// Initialize autocomplete on the newly created container
const search = autocomplete({
  container: containerRef.current,
  placeholder: "Search",
  plugins: [defaultUsagePlugin],
});
```

You would get the suggestions on search.


<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-6-default-usage-plugin/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

**Advanced usage:**

We can customize the UI rendered for suggestions, actions when a suggestion is clicked. We can do so by passing a third parameter to `createSuggestionPlugin`.

If we want to customize how suggestions get rendered then we can pass a third parameter with a property `renderItem`. It would be called by `autocomplete-suggestion-plugin` with `props` similar to [template's item method in autocomplete.](https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates/#rendering-each-item).

```js
//advancedUsagePlugin.js
const advancedUsagePlugin = createSuggestionsPlugin(
  appbaseClientConfig,
  rsApiConfig,
  {
    //props would be similar to template's item method.
    renderItem: (props) => {
      const { item } = props;
      return (
        <div className="aa-item product-item" target="_blank" rel="noreferrer">
          <div className="product-image">
            <img
              src={
                item._source
                  ? item._source.image
                  : "https://m.media-amazon.com/images/I/81c83vd4O+L._SY879_.jpg"
              }
              alt={item.value}
            />
          </div>
          <div className="product-details">
            <h4>{item.value} (Promoted)</h4>
            <p>
              {item._source
                ? item._source.shortDescription
                : "Samsung offers latest smartphones with advanced technology and design. Buy 3G, 4G, dual sim smartphone at best prices in India."}
            </p>
          </div>
        </div>
      );
    },
  }
);
```

Similarly, for customizing the UI for header, footer and no suggestions, `autocomplete-suggestion-plugin` provides us with `renderHeader`, `renderFooter` and `renderNoSuggestions`. Their usage is below.

```js
const advancedUsagePlugin = createSuggestionsPlugin(
  appbaseClientConfig,
  rsApiConfig,
  {
    renderHeader: () => {
      return <div className="header">Product listings</div>;
    },
    renderFooter: (props) => {
      return <hr style={{ borderColor: "#d7d5f5" }} />;
    },
    renderNoResults: (props) => {
      if (props.state.query === "") {
        return <p>Search for something to get direct product suggestions!</p>;
      } else {
        return <p>No products found! Try searching for something else!</p>;
      }
    },
  }
);
```

We can also customize the action when a user clicks on one of the suggestion displayed. This can be done by passing a callback function as a property `onItemSelect`.

```js
const advancedUsagePlugin = createSuggestionsPlugin(
  appbaseClientConfig,
  rsApiConfig,
  {
    onItemSelect: (props) => {
      const {
        item: { url, label, type },
        setQuery,
        refresh,
      } = props;

      if (url) {
        window.open(url, "_blank");
      }
    },
  }
);
```

Similar to default usage, add the plugin to autocomplete.

```js
// Initialize autocomplete on the newly created container
const search = autocomplete({
  container: containerRef.current,
  placeholder: "Search",
  plugins: [advancedUsagePlugin],
});
```

<iframe src="https://codesandbox.io/embed/github/appbaseio-apps/saas-search/tree/step-6-default-usage-plugin/?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden; margin-bottom: 2rem"
     title="omnibar"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>


# Live code and demo

Congratulations! We have built a Spotlight like SAAS search completely from scratch. You can [watch the demo](https://4pqmoi.csb.app/), and also [browse the code.](https://github.com/appbaseio-apps/saas-search)

# Summary

[Step 1: Setup an app using CRA](https://github.com/appbaseio-apps/saas-search/tree/step-1-cra)

[Step 2: Make searchbar using algolia autocomplete](https://github.com/appbaseio-apps/saas-search/tree/step-2-add-searchbar)

[Step 3: Add styles and themes](https://github.com/appbaseio-apps/saas-search/tree/step-3-custom-styles)

[Step 4: Show suggestions for github repositories](https://github.com/appbaseio-apps/saas-search/tree/step-4-github-repo-plugin)

[Step 5: Add functions as suggestions](https://github.com/appbaseio-apps/saas-search/tree/step-5-google-search)

[Step 6: Show suggestions from index](https://github.com/appbaseio-apps/saas-search/tree/step-6-advanced-usage-plugin)

