How to make Autocomplete in React.js with Fuse.js
Tags: react, autocomplete, fuse, guide
Published: 2021-08-29 01:42:51

![React Autocomplete guide](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ixch26r5txzkw6g2sqws.png)
In this guide I will teach you how to build Autocomplete input with Fuse.js and React.js

## Demo project setup

First up let's create new React app. This should setup React using `create-react-app`.
```command-line
npx create-react-app autocomplete-demo
cd autocomplete-demo
yarn start
```

Next we will need 2 extra dependencies [Fuse.js](https://fusejs.io/) and [Styled Components](https://styled-components.com/).

__Fuse.js__ will help us with fuzzy search on client side since we will not be storing user searches anywhere but on a client side.

__Styled Components__ is to make everything look pretty
 
Let's install those

```command-line
yarn add fuse.js styled-components
```
Now that everything is installed, let's get to coding part!

## Autocomplete component

First create folder named Autocomplete and index.js file
```command-line
mkdir Autocomplete
touch Autocomplete/index.js
```

There are 3 core elements in this Autocomplete component implementation:
- Input for entering text
- Suggestions list
- Shadow word match 

```jsx

export const Autocomplete = () => {
  const [searchTerm, setText] = useState("");
  const [searchHistory, setHistory] = useState([]);
  const handleSubmit = (ev) => {
    ev.preventDefault();
    const set = new Set([...searchHistory, searchTerm]);
    setHistory([...set]);
    setText("");
  };
  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="search" // this gives us ability to clear input with Esc key
          value={searchTerm}
          onChange={(ev) => setText(ev.target.value)}
          placeholder="eg. I do autocomplete for living"
        />
      </form>
      {/* suggestions list */}
      <div>
        <div show={searchTerm.length > 0 && searchHistory.length > 0}>
          <ol>
            {searchHistory.map((search) => (
              <li key={search}>{search}</li>
            ))}
          </ol>
        </div>
      </div>
    </div>
  );
};
```

This is what we have so far. Every time user submits search query we add it to searchHistory and show it in the list.
![step1](https://rem-blog-bucket.s3.amazonaws.com/assets/3f5de6ab-b8a2-4702-945f-9009604dac43-screen-shot-2021)

Now I know this is already looks very pretty but let's do some styling. Let's create `styles.js` file and add our styled-components there. 

```command-line
touch Autocomplete/styles.js
```

styles.js
```jsx
import styled from "styled-components";

export const AutocompleteContainer = styled.div`
  width: 450px;
  margin: 0 auto;
`;

export const SuggestionsContainer = styled.div`
  position: relative;
`;

export const SuggestionsDropdown = styled.div`
  position: absolute;
  width: 100%;
  border: 2px solid gainsboro;
  border-radius: 4px;
  margin-top: 2px;
  box-sizing: border-box;
  display: ${({ show }) => (show ? "block" : "none")};
`;

export const Input = styled.input`
  width: 100%;
  padding: 1.1rem;
  border: 2px solid gainsboro;
  border-radius: 4px;
  font-size: 1.2rem;
  z-index: 10;
  background: transparent;

  &:focus {
    outline: none;
    border-color: lightblue;
    box-shadow: 0 0 4px lightblue;
  }
`;

export const List = styled.ol`
  list-style: none;
  text-align: start;
  font-size: 1.1rem;
  padding: 0;
  margin: 0;
`;

export const SuggestionItem = styled.li`
  padding: 1.1rem;
  transition: all 250ms ease-in-out;
  &:hover {
    background: #cccccc;
  }
`;

export const MatchShadow = styled.div`
  position: absolute;
  border: 2px solid transparent;
  padding: 1.1rem;
  border-radius: 4px;
  font-size: 1.2rem;
  color: #cccccc;
  z-index: -1;
  user-select: none;
  background: transparent;
  top: 0;
`;
```
This should be enough, this contains enough styling for every element that we use.

Autocomplete
```jsx
import { useState } from "react";
import {
  AutocompleteContainer,
  Input,
  List,
  SuggestionItem,
  SuggestionsContainer,
  SuggestionsDropdown
} from "./styles";

export const Autocomplete = () => {
  const [searchTerm, setText] = useState("");
  const [searchHistory, setHistory] = useState([]);
  const handleSubmit = (ev) => {
    ev.preventDefault();
    const set = new Set([...searchHistory, searchTerm]);
    setHistory([...set]);
    setText("");
  };
  return (
    <AutocompleteContainer>
      <form onSubmit={handleSubmit} style={{ position: "relative" }}>
        <Input
          type="search"
          value={searchTerm}
          onChange={(ev) => setText(ev.target.value)}
          placeholder="eg. I do autocomplete for living"
        />
      </form>
      {/* suggestions list */}
      <SuggestionsContainer>
        <SuggestionsDropdown
          show={searchTerm.length > 0 && searchHistory.length > 0}
        >
          <List>
            {searchHistory.map((search) => (
              <SuggestionItem key={search}>{search}</SuggestionItem>
            ))}
          </List>
        </SuggestionsDropdown>
      </SuggestionsContainer>
    </AutocompleteContainer>
  );
};
```

## Fuse.js

Time to add fuse.js and make our Autocomplete somewhat smart in its suggestions.

```command-line
touch Autocomplete/useFuse.js
```

Here is a `useFuse` hook that we will use to make suggestions.

```javascript
import { useEffect, useRef, useState } from "react";
import Fuse from "fuse.js";

export function useFuse(searchTerm, items, options = {}) {
  const fuse = useRef();
  const [suggestions, setSuggestions] = useState([]);
  useEffect(() => {
    fuse.current = new Fuse(items, options);
  }, [items, options]);
  useEffect(() => {
    const items = fuse.current.search(searchTerm);
    setSuggestions(items.map(({ item }) => item));
  }, [searchTerm]);

  return suggestions;
}
```
Every time we update `searchTerm` fuse will run search on that updated term and set new suggestions based on it. 

Injecting `useFuse` into Autocomplete component
```diff
import { useState } from "react";
import {
  AutocompleteContainer,
  Input,
  List,
  MatchShadow,
  SuggestionItem,
  SuggestionsContainer,
  SuggestionsDropdown
} from "./styles";
+import { useFuse } from "./useFuse";

export const Autocomplete = () => {
  const [searchTerm, setText] = useState("");
  const [searchHistory, setHistory] = useState([]);
  const handleSubmit = (ev) => {
    ev.preventDefault();
    const set = new Set([...searchHistory, searchTerm]);
    setHistory([...set]);
    setText("");
  };
+  const suggestions = useFuse(searchTerm, searchHistory);
+  const exactMatch = (query, text) => {
+    const regex = new RegExp(`^${query}`);
+    return regex.test(text);
+  };
  return (
    <AutocompleteContainer>
      <form onSubmit={handleSubmit} style={{ position: "relative" }}>
        <Input
          type="search"
          value={searchTerm}
          onChange={(ev) => setText(ev.target.value)}
          placeholder="eg. Mazda, Toyota, Porshe"
        />
+        <MatchShadow>
+          {suggestions.length > 0 &&
+            exactMatch(searchTerm, suggestions[0]) &&
+            suggestions[0]}
+        </MatchShadow>
      </form>
      {/* suggestions list */}
      <SuggestionsContainer>
        <SuggestionsDropdown
          show={searchTerm.length > 0 && suggestions.length > 0}
        >
          <List>
            {suggestions.map((search) => (
              <SuggestionItem key={search}>{search}</SuggestionItem>
            ))}
          </List>
        </SuggestionsDropdown>
      </SuggestionsContainer>
    </AutocompleteContainer>
  );
};

```

This block adds usage of `useFuse` and pipes in searchHistory and searchTerm. 
```jsx
  const suggestions = useFuse(searchTerm, searchHistory);
  const exactMatch = (query, text) => {
    const regex = new RegExp(`^${query}`);
    return regex.test(text);
  };
```

This is a helper function that will check if suggestion is exact match with query that user types in. If yes we will show autocomplete shadow of the suggested word in the input. Giving it a very nice touch for UX.
```jsx
  const exactMatch = (query, text) => {
    const regex = new RegExp(`^${query}`);
    return regex.test(text);
  };
```

Finally here we add `MatchShadow` styled component and adding our exact match and other conditional checks to make sure we show it only when we have suggestions and it is an exact match. 
```jsx
        <MatchShadow>
          {suggestions.length > 0 &&
            exactMatch(searchTerm, suggestions[0]) &&
            suggestions[0]}
        </MatchShadow>
```

## Result

With all that in place let's check final result!

After user types in few searches and submits them 
![Populate history](https://rem-blog-bucket.s3.amazonaws.com/assets/7d617b95-dc57-4a5c-a681-8f4e8c73033e-screen-shot-2021)

And if user request is exact match from previous search
![exact match search](https://rem-blog-bucket.s3.amazonaws.com/assets/2c9ff001-7a58-4ba1-8a34-26bf464cab23-screen-shot-2021)

### I hope you found this guide useful! Thank you for reading.


## Links
- [Source code](https://github.com/rem4ik4ever/autocomplete-demo)
- [Sandbox](https://codesandbox.io/s/polished-firefly-ur2of?file=/src/Autocomplete/index.js)
- [My Blog](https://remkim.com)