Live Search in React js - Select with Mouse or Keyboard
Tags: react, webdev, javascript, tutorial
Published: 2022-09-11 16:55:36

Displaying the live search results to the user while they are typing in the search bar is a great user experience. But creating the live search field with all the features. Like, select with a mouse, move selections on key up down and hide the search bar on blur.

![Final Live Search Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm1rurh45iyqgg10f32v.gif)

This is a little bit of a challenging task. So, let's try to go through all the challenges and create this awesome live search field. Where users can select the result with a mouse or by using the keyboard also the results will be hidden if we click outside.

The things we will cover
- [React & Tailwind Project Setup](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#React%20&%20Tailwind%20Project%20Setup)
- [Creating a Search Bar](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Creating%20a%20Search%20Bar)
- [Container to Render Search Results](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Container%20to%20Render%20Search%20Results)
- [Creating a Fully Customizable Component](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Creating%20a%20Fully%20Customizable%20Component)
- [Conditionally Rendering Results](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Conditionally%20Rendering%20Results)
- [Adding a Live Search Logic](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Adding%20a%20Live%20Search%20Logic)
- [Selecting Results on Key up-down](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Selecting%20Results%20on%20Key%20up-down)
- [Styling the Focused Item](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Styling%20the%20Focused%20Item)
- [Selecting the Results](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Selecting%20the%20Results)
- [Hiding Results onBlur](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Hiding%20Results%20onBlur)
- [Updating the value inside the search bar](https://www.fsniraj.dev/blogs/live-search-in-react-js-select-with-mouse-or-keyboard#Updating%20the%20value%20inside%20the%20search%20bar)

## React & Tailwind Project Setup

To create this demo we are going to use obviously the React JS and Tailwind CSS for styling. If you want to you can use any other CSS framework or library. This logic will work for all of them.

> If you don't know Tailwind CSS is a framework with a bunch of utility class names to style our HTML.

If you already have a project, you can directly add this logic to your existing project as well. But if you are starting from the ground like me run these commands to initialize a new project.

```
npx create-react-app live-search --template typescript
# or
npx create-react-app live-search // if you are using JavaScript
```

Once you run one of the commands, the project will be initialized. Now you can install Tailwind CSS. First, change the directory with `cd live-search` and run these commands.

```
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

Then you have to go to your `tailwind.config.js` file and add these things inside your content.

```
// tailwind.config.js
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}",],
...
```
 Then go to your `index.css` and replace all of the code with these three lines of code.


```
// index.css
// remove old CSS and add them
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Creating a Search Bar

A search bar is just an `input` element. But we will add a little bit of style to it so that it looks a little bit nice. For that, let's create a file called `SearchBar.tsx` (or jsx) inside the src folder and add these codes.

```
// inside SearchBar.tsx
import React, { FC } from "react";

interface Props<T> {
  value?: string;
  notFound?: boolean;
  onChange?: React.ChangeEventHandler;
  results?: T[];
  renderItem: (item: T) => JSX.Element;
  onSelect?: (item: T) => void;
}

const SearchBar = <T extends object>({ }: Props<T>): JSX.Element => {
  return (
    {/* Search bar */}
    <input
      type="text"
      className="w-full p-2 text-lg rounded border-2 border-gray-500 outline-none"
      placeholder="Search here..."
    />
  );
};
```

I don't think I have to explain these class names. They are explaining themselves. But these `interface Props` might look a little scary. This is just a Typescript thing which I will explain when we need them. So, let's render this component inside our `App.tsx`

```
// inside App.tsx
const App = () => {
  return (
    <div className="max-w-3xl mx-auto mt-10">
      <SearchBar />
    </div>
  );
};

export default App;
```

Here I am using the fixed width for the `div` so that I can use this `mx-auto` to bring this `div` to the center of the webpage. If you try to run this code it will throw an error. Because `renderItem` is the required prop. Comment it out if you want to see the change.

## Container to Render Search Results

Now we need a container to render our results. So for that, we will wrap our search input inside a `div` with class `relative`. And we will render a `div` below the input element with class absolute. With these classes, we will render the search container right below the input field.

```
// SearchBar.tsx

const SearchBar = <T extends object>({ }: Props<T>): JSX.Element => {
  return (
    <div className="relative">
      <input ...  />
     {/* Results Wrapper */}
      <div className="absolute mt-1 w-full p-2 bg-white shadow-lg rounded-bl rounded-br max-h-36 overflow-y-auto"></div>
    </div>
  );
};
```

With the help of other classes, like `max-h-36 overflow-y-auto`. It will create a container with a fixed height and show the scroll bar if the results are longer.

## Creating a Fully Customizable Component

Now let's use the first props from inside the interface.  As you see here we are using a little weird syntax for the `interface` and `SearchBar` components.

```
interface Props<T> { 
  results?: T[];
  renderItem: (item: T) => JSX.Element;
  onSelect?: (item: T) => void;
}

const SearchBar = <T extends object>({}:Props<T>) ...
```

This is because we want to make this search bar so customizable. So that, it can take, render and give the same data type back to our `renderItem` and `onSelect` methods. Now you don't need to worry about types anymore. Typescript will grab the type from `results` and distribute it to `renderItem` and `onSelect`.

Now finally see how we can render our results.

```
// inside SearchBar.tsx
const SearchBar = <T extends object>({ results = [], renderItem }: Props<T>): JSX.Element => {
  return (
    <div className="relative">
      {/* Search bar */}
      <input ... />
      {/* Results Wrapper */}
      <div className="...">
        {/* Results Lists Goes here */}
        <div className="space-y-2">
          {results.map((item, index) => {
            return (
              <div
                key={index}
                className="cursor-pointer hover:bg-black hover:bg-opacity-10 p-2"
              >
                {renderItem(item)}
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
};
```

Very first thing, don't forget to give your `results` prop a default value empty array `[]`. It will prevent unwanted errors. Then we are using `resutls.map` to render our results. Where we have `renderItem` method to render results. We have to pass this method from `App.tsx` which we will see later. 

Then we have a wrapper div for `renderItem` with classes, `hover:bg-black hover:bg-opacity-10`. It is very important to give the user feedback on results hover. These classes will add `background-color` of black with `opacity: 0.1` on the result hover.

Ok, now let's try to pass some data to our `SearchBar`. Because we used that `<T>` generic type we can pass any type of data but inside an array.

```
const profiles = [
  { id: "1", name: "Allie Grater" },
  { id: "2", name: "Aida Bugg" },
  { id: "3", name: "Gabrielle" },
  { id: "4", name: "Grace" },
  { id: "5", name: "Hannah" },
  { id: "6", name: "Heather" },
  { id: "7", name: "John Doe" },
  { id: "8", name: "Anne Teak" },
  { id: "9", name: "Audie Yose" },
  { id: "10", name: "Addie Minstra" },
  { id: "11", name: "Anne Ortha" },
];
const App = () => {
  return (
    <div className="max-w-3xl mx-auto mt-10">
      <SearchBar results={profiles} renderItem={(item) => <p>{item.name}</p>} />
    </div>
  );
};
``` 
As I told you earlier you don't need to worry about the types for `renderItem` and `onSelect`. Here is the result if you hover over an item you will see its type. Cool right?

![typescript in action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjuxhshtuya01trnl0xp.png)

Now if you save your file and run the project with `npm start`. You will see something like this.

![search results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dyzgc1na0l1ldi95x0lu.png)

## Conditionally Rendering Results

Currently, these results are always visible so, let's solve this issue. For that, we can simply add a state called `showResults` and display the results according to the value of this state.

```
// inside SearchBar.tsx
const SearchBar = ... {
  const [showResults, setShowResults] = useState(true);

  // To toggle the search results as result changes
  useEffect(() => {
    if (results.length > 0 && !showResults) setShowResults(true);

    if (results.length <= 0) setShowResults(false);
  }, [results]);
   
   return ...
}
```

The logic is pretty straightforward if there are results and the `showResults` state is `false` only then make it `true`. Otherwise, make it false. These logics will now hide and show our search results as the result changes its length.

But for that, we have to add the condition if `showResults` is `true` only then render the results.

```
const SearchBar = ... {
 ...
  return (
    <div className="relative">
      {/* Search bar */}
      <input ... />
      {/* Results Wrapper */}
      {showResults ? <div className="...">
        {/* Results Lists Goes here */}
        <div className="space-y-2">...</div>
      </div> : null}
    </div>
  );
};
```

## Adding a Live Search Logic

Now let's write a little piece of code to add a fake search logic.

```
// SearchBar.tsx
const SearchBar = <T extends object>({onChange, ... }: Props<T>): JSX.Element => {
return (...
  <input
     onChange={onChange}
   />
...

// App.tsx

const profiles = [...]
const App = () => {
  const [results, setResults] = useState<{ id: string; name: string }[]>();
  
type changeHandler = React.ChangeEventHandler<HTMLInputElement>;
  const handleChange: changeHandler = (e) => {
    const { target } = e;
    if (!target.value.trim()) return setResults([]);

    const filteredValue = profiles.filter((profile) =>
      profile.name.toLowerCase().startsWith(target.value)
    );
    setResults(filteredValue);
  };

  return (
    <div className="max-w-3xl mx-auto mt-10">
      <SearchBar
        onChange={handleChange}
        results={results}
        renderItem={(item) => <p>{item.name}</p>}
      />
    </div>
  );
};
```

Even though this is too much code the logic is simple. First, we destructure the `onChange` inside `SearchBar` component and assigned it to the input element.

Then inside `App.tsx` we are handling the change event for `SearchBar`. So for the `handleChange` we are just using the `array filter` method to filter out the matching results. 

If we found the results we will add them to the new state called results and if we have an empty input field we will reset those results to an empty array.

And at the end instead of the `profiles`, we will pass the `results` state to the `results` prop. If you do this you should see the search results only if you type out the correct profile names.

![live searchresults](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gljrmqkqd0jz8qn2ij85.png)

Inside the `handleChange` you can also fetch the data from the server. The only important thing is how will you add or reset your `results` state. Because if you forgot to reset the `results` state the results inside your live search container will be visible all the time.

## Selecting Results on Key up-down

It's nice we have our search bar where we can search and render the results. But now we need to add a keyboard event to move our selection up and down.

```
const SearchBar = ... => {
const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
    const { key } = e;

    // move down
    if (key === "ArrowDown"){ }

    // move up
    if (key === "ArrowUp"){ }

   // hide search results
    if (key === "Escape"){ }

   // select the current item
    if (key === "Enter"){ }
};

return (
<div
      tabIndex={1}
      onKeyDown={handleKeyDown}
      className="relative outline-none"
    >
   <input ... />
   ...
```

We want to now listen to the `keyDown` event and we are doing that to the wrapping `div` inside `SearchBar` component. But we can not directly listen to the `keyDown` for divs.  So we need to use the `tabIndex` prop. Also to avoid unwanted outlines on div focus (because we are using `tabIndex` there will be an outline) I am using the `outline-none` class.

Let's write down the keyboard logic one by one.

Handling the keyboard event is not that straightforward. Because while we are moving up or down we have to handle multiple things. Like displaying the correct UI, and scrolling in the right position if there are multiple results.

So for that first, we need a state called `focusedIndex`. As you can see the default value for this state is -1. Because later we will use this state to move our focus to the correct place. And we don't want to focus on anything at first. So -1 will work fine.

```
// inside SearchBar.tsx
const SearchBar = ... => {
  const [focusedIndex, setFocusedIndex] = useState(-1);

...
```

Now with the logic below it will update our focused index with the correct timing. Also if you notice I am not using -1 or +1 to update our value. Instead, we are using the `modulo` (%) operater.

This is a small formula that we can use to update our count only within the range according to the length of the results. So if you have results with the length 6 and if you press the down key then the `nextCount` will start from 0 and goes to 5. If you press up this will be the opposite starts from 5 and goes to 0.

Which is the exact value that we need to be inside the bound of the result array. If we go beyond the length we will get _index bound exception_.

```
  const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
    let nextCount = 0;
    if (key === "ArrowDown") nextCount = (focusedIndex + 1) % results.length;

    if (key === "ArrowUp") nextCount = (focusedIndex + results.length - 1) % results.length;
    
    if (key === "Escape") setShowResults(false);

    if (key === "Enter") {
      e.preventDefault();
    }

    setFocusedIndex(nextCount);
  };
```

Also, there is one more thing happening. If we press the `Escape` key we will hide the search results. But we are not handling the `Enter` we will do that later.

## Styling the Focused Item

Now according to the `focusedIndex` we need to style the result. For that, we have to take a reference of specific results and change the style.

```
// SearchBar.tsx
const SearchBar = ... => {
 const resultContainer = useRef<HTMLDivElement>(null);
 return (
 ...
 {results.map((item, index) => {
  return ( 
   <div
    ref={index === focusedIndex ? resultContainer : null}
    style={{ backgroundColor: index === focusedIndex ? "rgba(0,0,0,0.1)" : "" }}
    key={index}
    className="cursor-pointer hover:bg-black hover:bg-opacity-10 p-2"
    >
      {renderItem(item)}
    </div>
    );
})}
```

The main thing for this `div` is the `ref` and the `style` prop. What basically we are doing? If the `index` is equal to the `focusedIndex` we are assigning this `div` to the `ref` called `resultContainer` otherwise we are making it null. Why? You will understand it later.

Then we are changing the background color to black with an opacity of 10% if the `index` is equal to the `focusedIndex`. 

Now with this logic, if you try to move up or down you will see the change but the results will stay in one place only. It will not scroll down or up as you update your index. To fix this use this logic.

```
// SearchBar.tsx
  useEffect(() => {
    if (!resultContainer.current) return;

    resultContainer.current.scrollIntoView({
      block: "center",
    });
  }, [focusedIndex]);
```

If there is `resultContainer` we will use `scrollIntoView` method to scroll in to the exact point and we will make the focused item in the center with the option `block: center`.

Now you should have something like this.

![live search animated gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ay1s60usgcdaid9ee145.gif)

## Selecting the Results

One of the important things for live search is selecting the result so let's see how we can do that.

```
// SearchBar.tsx
...
  const handleSelection = (selectedIndex: number) => {
    const selectedItem = results[selectedIndex];
    if (!selectedItem) resetSearchComplete();
    onSelect && onSelect(selectedItem);
    resetSearchComplete();
  };

  const resetSearchComplete = useCallback(() => {
    setFocusedIndex(-1);
    setShowResults(false);
  }, []);
...
```

We need to now handle two things for selection first select the correct thing and send it back to the `App.tsx`. Because this is the component where we are using it right now. Then we also need to reset the search results. 

For that, inside `handleSelection` we are accepting `selectedIndex` and extracting the selected item from inside the results array. If there is nothing just return otherwise call the `onSelect` with the selected result. Make sure you are destructuring it from the props.

```
const SearchBar = ({ onSelect, ...}) => 
```
 And then simply reset the states to their previous values. `focusedIndex` to -1 and `showResults` to false.

Now we need to add this `handleSelection` in two places. Inside Enter key press and on mouse down for the result item.

```
// SearchBar.tsx
// to select on enter
  const handleKeyDown: React.KeyboardEventHandler<HTMLDivElement> = (e) => {
   ...
    if (key === "Enter") {
      e.preventDefault();
      handleSelection(focusedIndex);
    }

    setFocusedIndex(nextCount);
  };


return  (
...
{results.map((item, index) => {
  return ( 
   <div
    onMouseDown={() => handleSelection(index)}
    ref={index === focusedIndex ? resultContainer : null}
    style={{ backgroundColor: index === focusedIndex ? "rgba(0,0,0,0.1)" : "" }}
    ...
    >
      {renderItem(item)}
    </div>
    );

```

With this now if you try to select with a mouse or keyboard you will select the correct item. If you want to see that add `onSelect` inside your `SearchBar` and log the result to the console.

```
// App.tsx
const App = () => {
  return (
    <div className="max-w-3xl mx-auto mt-10">
      <SearchBar
        ...
        onSelect={(item) => console.log(item)}
        />
    </div>
  );
}
```

## Hiding Results onBlur

Now if you are thinking why `onMouseDown` why not `onClick`? Instead of giving you the answer, I will show you that. 

Just add `onBlur` to your top wrapper `div` inside `SearchBar` component. And pass `resetSearchComplete` method to it.

```
// SearchBar.tsx
...
return (
    <div
      tabIndex={1}
      onKeyDown={handleKeyDown}
      onBlur={resetSearchComplete}
      className="relative outline-none"
    >
...
```

Now if you click outside the search results will be gone which is the perfect result we want. Also, you will see the selected results inside your console (if you select any). But now if you change that `onMouseDown` to `onClick` results will be gone from the screen but you will not get the selected item inside the console.

Hmmm, why is that? This is because before `onClick` the `onBlur` will be fired. So, `handleSelection` will be never called.


## Updating the value inside the search bar

Everything looks fine here. The only thing that we need now is to display the selected profile name inside our search bar. So, create a state called `defaultValue` inside `SearchBar.tsx`. Then pass this value to the `input` element.

```
// SearchBar.tsx
const SearchBar = ... ({ value }) => {
  const [defaultValue, setDefaultValue] = useState("");
...
return ...
   <input
     ...
     value={defaultValue}
   />
```

If you remember we are also accepting a prop called value for our `SearchBar` component. So, if there is `value` prop available let's assign that to the `defaultValue`.

```
// SearchBar.tsx
  useEffect(() => {
    if (value) setDefaultValue(value);
  }, [value]);
```

Now if the value changes this `useEffect` hook will update the `defaultValue` to the `value`.

Let's do one more thing inside `App.tsx`. Add a state called `selectedProfile` and update the value inside `onSelect`. Then pass `selectedProfile.name` to the value `prop`.

```
// App.tsx
const App = () => {
  const [selectedProfile, setSelectedProfile] = useState<{ id: string; name: string }>();   
   return (
       ...
      <SearchBar
         ...
        onSelect={(item) => setSelectedProfile(item)}
        value={selectedProfile?.name}
      />
```

Now ladies and gentlemen you should see something like this.


![live search updating input value](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tezz6cprvhzwygzsnea4.gif)

But there is one big problem. Once you have selected the result you can not make any changes to the input field.

To solve this issue we have to handle the `onChange` event directly inside our `SearchBar` component.

```
// SearchBar.tsx

const SearchBar = ({ onChange }) => {
  ...
  type changeHandler = React.ChangeEventHandler<HTMLInputElement>;
  const handleChange: changeHandler = (e) => {
    setDefaultValue(e.target.value);
    onChange && onChange(e);
  };

return (
...
      {/* Search bar */}
      <input
        onChange={handleChange}
...
```

Here instead of passing `onChange` prop directly to the `input` element. Now we will pass `handlChange` and inside here we will update the `defaultValue` and then call the `onChange` prop. With this now you can update the value with `onSelect` and this `SearchBar` will automatically reset the value when the `onChange` occurs.

And I think this is more than enough for this project. You can see the final look and if you have anything to say at the bottom you will find my social media links.

![Final Live Search Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sm1rurh45iyqgg10f32v.gif)