Open In App

Multi Select Search Using ReactJS

Last Updated : 13 Feb, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

Multi Select Search Using ReactJS enables users to search for and select multiple items from a list simultaneously, enhancing usability and efficiency in interfaces like forms or tag selection. React interviews often include challenges related to building interactive and user-friendly components. One such common task is creating a multi-select search functionality. This article will guide you through a practical example of implementing a multi-select search component using React.

Prerequisites:

Approach to implement Multi Select Search:

The goal is to create a multi-select search component where users can type a query, see suggestions, and select multiple options. The selected options will be displayed as pills, and users can remove them individually.

Steps to Create the Project:

  • Step 1: Set Up Your React App with Vite:
npm create vite@latest
  • Step 2: Navigate to the Project Directory
cd multi-select-search
  • Step 3: Install the package dependency.
npm install

Project Structure:

Screenshot-2024-02-09-185123

project structure

The updated dependencies in package.json file will look like:

"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"web-vitals": "^2.1.4"
}

Javascript




// App.jsx
import { useEffect, useRef, useState } from "react";
import "./App.css";
import Pill from "./components/Pill";
 
function App() {
    const [searchTerm, setSearchTerm] = useState("");
    const [suggestions, setSuggestions] = useState([]);
    const [selectedUsers, setSelectedUsers] = useState([]);
    const [selectedUserSet, setSelectedUserSet] = useState(new Set());
    const [activeSuggestion, setActiveSuggestion] = useState(0);
 
    const inputRef = useRef(null);
 
    useEffect(() => {
        const fetchUsers = () => {
            setActiveSuggestion(0);
            if (searchTerm.trim() === "") {
                setSuggestions([]);
                return;
            }
 
            fetch(`https://dummyjson.com/users/search?q=${searchTerm}`)
                .then((res) => res.json())
                .then((data) => setSuggestions(data))
                .catch((err) => {
                    console.error(err);
                });
        };
 
        fetchUsers();
    }, [searchTerm]);
 
    const handleSelectUser = (user) => {
        setSelectedUsers([...selectedUsers, user]);
        setSelectedUserSet(new Set([...selectedUserSet, user.email]));
        setSearchTerm("");
        setSuggestions([]);
        inputRef.current.focus();
    };
 
    const handleRemoveUser = (user) => {
        const updatedUsers = selectedUsers.filter(
            (selectedUser) => selectedUser.id !== user.id
        );
        setSelectedUsers(updatedUsers);
 
        const updatedEmails = new Set(selectedUserSet);
        updatedEmails.delete(user.email);
        setSelectedUserSet(updatedEmails);
    };
 
    const handleKeyDown = (e) => {
        if (
            e.key === "Backspace" &&
            e.target.value === "" &&
            selectedUsers.length > 0
        ) {
            const lastUser = selectedUsers[selectedUsers.length - 1];
            handleRemoveUser(lastUser);
            setSuggestions([]);
        } else if (e.key === "ArrowDown" &&
            suggestions?.users?.length > 0) {
            e.preventDefault();
            setActiveSuggestion((prevIndex) =>
                prevIndex < suggestions.users.length - 1 ?
                    prevIndex + 1 : prevIndex
            );
        } else if (e.key === "ArrowUp" &&
            suggestions?.users?.length > 0) {
            e.preventDefault();
            setActiveSuggestion((prevIndex) =>
                (prevIndex > 0 ? prevIndex - 1 : 0));
        } else if (
            e.key === "Enter" &&
            activeSuggestion >= 0 &&
            activeSuggestion < suggestions.users.length
        ) {
            handleSelectUser(suggestions.users[activeSuggestion]);
        }
    };
 
    return (
        <div className="user-search-container">
            <div className="user-search-input">
                {/* Pills */}
                {selectedUsers.map((user) => {
                    return (
                        <Pill
                            key={user.email}
                            image={user.image}
                            text={`${user.firstName} ${user.lastName}`}
                            onClick={() => handleRemoveUser(user)}
                        />
                    );
                })}
                {/* input feild with search suggestions */}
                <div>
                    <input
                        ref={inputRef}
                        type="text"
                        value={searchTerm}
                        onChange={(e) => setSearchTerm(e.target.value)}
                        placeholder="Search For a User..."
                        onKeyDown={handleKeyDown}
                    />
                    {/* Search Suggestions */}
                    {searchTerm && (
                        <ul className="suggestions-list">
                            {suggestions?.users?.map((user, index) => {
                                return !selectedUserSet.has(user.email) ? (
                                    <li
                                        className={index === activeSuggestion ?
                                            "active" : ""}
                                        key={user.email}
                                        onClick={() => handleSelectUser(user)}
                                    >
                                        <img
                                            src={user.image}
                                            alt={`${user.firstName}
                                                  ${user.lastName}`}
                                        />
                                        <span>
                                            {user.firstName} {user.lastName}
                                        </span>
                                    </li>
                                ) : (
                                    <></>
                                );
                            })}
                        </ul>
                    )}
                </div>
            </div>
        </div>
    );
}
 
export default App;


Javascript




// main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
 
ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);


Javascript




// Pill.jsx
const Pill = ({ image, text, onClick }) => {
  return (
    <span className="user-pill" onClick={onClick}>
      <img src={image} alt={text} />
      <span>{text} ×</span>
    </span>
  );
};
 
export default Pill;


CSS




/* App.css  */
body {
  font-family: sans-serif;
}
 
.user-search-container {
  display: flex;
  position: relative;
}
 
.user-search-input {
  width: 100%;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 5px;
  border: 1px solid #ccc;
  border-radius: 20px;
}
 
.user-search-input input {
  border: none;
  height: 20px;
  padding: 5px;
}
 
.user-search-input input:focus {
  outline: none;
}
 
.suggestions-list {
  max-height: 300px;
  overflow-y: scroll;
  list-style: none;
  padding: 0;
  margin: 0;
  position: absolute;
  background-color: #fff;
  border: 1px solid #ccc;
}
 
.suggestions-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  cursor: pointer;
  border-bottom: 1px solid #ccc;
}
 
.suggestions-list li:last-child {
  border-bottom: none;
}
 
.suggestions-list li:hover {
  background-color: #ccc;
}
 
.suggestions-list li img {
  height: 20px;
}
 
.user-pill {
  height: 20px;
  display: flex;
  align-items: center;
  gap: 5px;
  background-color: black;
  color: #fff;
  padding: 5px 10px;
  border-radius: 16px;
  cursor: pointer;
}
 
.user-pill img {
  height: 100%;
}
.suggestions-list li.active {
  background-color: #ccc;
}


Steps to run the application:

npm run dev

Output:

Recording-2024-02-09-192915-(1)

Output



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads