Open In App

Multi Select Search Using ReactJS

Last Updated : 25 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
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"
}
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:


Next Article
Article Tags :

Similar Reads