Horje
Multi Select Search Using ReactJS

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"
}
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;
}
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;

Steps to run the application:

npm run dev

Output:




Reffered: https://www.geeksforgeeks.org


ReactJS

Related
React Chakra UI Other React Chakra UI Other
React Chakra UI Typography Heading React Chakra UI Typography Heading
What is the significance of the componentDidMount lifecycle method? What is the significance of the componentDidMount lifecycle method?
Describe the applyMiddleware function in Redux. Describe the applyMiddleware function in Redux.
Next JS Middleware: Extending Functionality in Your Application Next JS Middleware: Extending Functionality in Your Application

Type:
Geek
Category:
Coding
Sub Category:
Tutorial
Uploaded by:
Admin
Views:
17