CUSTOM COMPONENT

The simplest multi-select with the Select All button in React

Just copy the code and use it! Fast implementation, without external libraries installation.

ProgrammingCouple
7 min readJan 17, 2024
How the multiple select component created in this tutorial works
How the multiple select component created in this tutorial works

Let’s start with the application setup. Open VS Code (or any other favorite code editor you use) and navigate in your terminal to the location where you want to create our new project. Put to your terminal the following command and hit enter:

npx create-react-app multiple-select --template typescript

And next:

npm i --save-dev @types/react

Then open just created folder in the location you have chosen. I will use the shortcut in the terminal to quickly open my project folder. To enable the same shortcut in your terminal, check out the quick tutorial HERE.

cd multiple-select
code .

We need to clean up the default given code. Delete the following files:

  • App.test.tsx
  • logo.svg
  • react-app-env.d.ts
  • reportWebVitals.ts
  • setupTests.ts

Next, remove useless parts of code like so:

  • App.css — remove all current styles (leave empty file)
  • App.tsx — remove unused imports and all given content; leave simple div; change syntax from function declaration to function expression (last one is optional)
  • index.css — no changes
  • index.tsx — remove comments, unused import, and reportWebVitals function call

After changes, your code should look like this:

// File name: App.tsx

import "./App.css";

const App = () => {
return <div>Hello world</div>;
};

export default App;
// File name: index.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";

const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

Now it’s time to run our code and test it works. You should see “Hello world” on the left top corner of the white screen. Provide the command below to your terminal and hit enter:

npm start

Let’s start with a simple container, which will be our future multiple-select component.

// File name: App.tsx

import "./App.css";

const App = () => {
return (
<div className="container">
<div className="multiple-select">
<div className="placeholder">Choose at least one item</div>
</div>
</div>
);
};

export default App;

As you can see, I add some styles. My App.css file looks like this:

/* File name: App.css */

.container {
display: flex;
flex-direction: column;
margin: 50px;
}

.multiple-select {
width: 250px;
height: 40px;
border: 1px solid black;
display: flex;
align-items: center;
justify-content: space-evenly;
cursor: pointer;
}

.placeholder {
margin-left: 10px;
width: 100%;
}

To create toggle functionality we should add useState imported from react library to handle state changes and track the information to open or close our select component. Then we add a simple custom triangle icon with an onClick event. Last, but not least, we’ll create a box that opens or closes depending on the current toggle state. Here is the code:

// File name: App.tsx

import { useState } from "react";
import "./App.css";

const App = () => {
// <----- NEW CODE ----->
const [toggleOpen, setToggleOpen] = useState(false);

return (
<div className="container">
<div
className="multiple-select"
onClick={() => setToggleOpen((toggle) => !toggle)}
>
<div className="placeholder">Choose at least one item</div>
<div
className={`icon ${toggleOpen ? "triangle-up" : "triangle-down"}`}
/>
</div>
{/* NEW CODE */}
{toggleOpen && (
<div className="dropdown">
<div>Options</div>
</div>
)}
</div>
);
};

export default App;
/* File name: App.css */

/*

<- previous classes go here ->

*/

.icon {
width: 0;
height: 0;
border-left: 7.5px solid transparent;
border-right: 7.5px solid transparent;
margin: 10px;
}

.triangle-up {
border-bottom: 12.5px solid #555;
}

.triangle-down {
border-top: 12.5px solid #555;
}

.dropdown {
width: 250px;
height: 100px;
border: 1px solid black;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0;
}

The next step is to replace our temporary Options word with a scrollable list of items. To do that, first, create a file named DEFAULT_DATA.ts in the ./src directory. The file will store example data that we use later. You can create your data file. The most important thing for the consistency of the following solutions is to keep the structure as an array of strings.

// File name: DEFAULT_DATA.ts

export const DEFAULT_DATA = [
"Dog",
"Cat",
"Hamster",
"Parrot",
"Spider",
"Goldfish",
"Horse",
"Lion",
"Rabbit",
"Cow",
"Donkey",
"Duck",
"Goat",
"Pig",
"Sheep",
"Turkey",
"Elephant",
"Giraffe",
"Monkey",
"Zebra",
"Moose",
"Dolphin",
"Snake",
"Emu",
"Tiger",
"Shark",
"Shrimp",
"Fish",
"Frog",
];

Below you find our current code with new things added. We have a new state to keep track of the list of selected items and onChange event handling. Pay attention to the line when we declare a new variable called updatedItems — here we make a copy of the selectedItems state because it’s immutable and we can’t change it directly.

// File name: App.tsx

import React, { useState } from "react";
import "./App.css";
import { DEFAULT_DATA } from "./DEFAULT_DATA";

const App = () => {
const [toggleOpen, setToggleOpen] = useState(false);

// <----- NEW CODE ----->
const [selectedItems, setSelectedItems] = useState<string[]>([]);

// <----- NEW CODE ----->
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedItem = event.target.value;

let updatedItems = [...selectedItems];

if (selectedItems.includes(selectedItem)) {
updatedItems = selectedItems.filter((item: string) => item !== selectedItem);
} else {
updatedItems.push(selectedItem);
}

setSelectedItems(updatedItems);
};

return (
<div className="container">
<div
className="multiple-select"
onClick={() => setToggleOpen((toggle) => !toggle)}
>
<div className="placeholder">Choose at least one item</div>
<div
className={`icon ${toggleOpen ? "triangle-up" : "triangle-down"}`}
/>
</div>
{toggleOpen && (
<div className="dropdown">
<div>
{/* NEW CODE */}
{DEFAULT_DATA.map((d) => (
<div key={d} className="dropdown-item">
<input
type="checkbox"
id={d}
name={d}
value={d}
onChange={handleChange}
checked={selectedItems.includes(d)}
/>
<label htmlFor={d} className="label">
{d}
</label>
</div>
))}
</div>
</div>
)}
</div>
);
};

export default App;

And we need to add a few simple lines of code to our CSS file:

/* File name: App.css */

/*

<- previous classes go here ->

*/

.dropdown {
/*

<- previous dropdown properties go here ->

*/
overflow: scroll;
}

.dropdown-item > * {
cursor: pointer;
}

.label {
margin-left: 5px;
}

We have two last features left to add: dynamically generated placeholder and Select all checkboxes. Let’s start with the first one:

// File name: App.tsx

import React, { useState } from "react";
import "./App.css";
import { DEFAULT_DATA } from "./DEFAULT_DATA";

const App = () => {
const [toggleOpen, setToggleOpen] = useState(false);
const [selectedItems, setSelectedItems] = useState<string[]>([]);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedItem = event.target.value;

let updatedItems = [...selectedItems];

if (selectedItems.includes(selectedItem)) {
updatedItems = selectedItems.filter((item) => item !== selectedItem);
} else {
updatedItems.push(selectedItem);
}

setSelectedItems(updatedItems);
};

// <----- NEW CODE ----->
const placeholderText = selectedItems.length
? `${selectedItems.length} ${
selectedItems.length === 1 ? "item" : "items"
} selected`
: "Choose at least one item";

return (
<div className="container">
<div
className="multiple-select"
onClick={() => setToggleOpen((toggle) => !toggle)}
>
{/* NEW CODE */} <div className="placeholder">{placeholderText}</div>
<div
className={`icon ${toggleOpen ? "triangle-up" : "triangle-down"}`}
/>
</div>
{toggleOpen && (
<div className="dropdown">
<div>
{DEFAULT_DATA.map((d) => (
<div key={d} className="dropdown-item">
<input
type="checkbox"
id={d}
name={d}
value={d}
onChange={handleChange}
checked={selectedItems.includes(d)}
/>
<label htmlFor={d} className="label">
{d}
</label>
</div>
))}
</div>
</div>
)}
</div>
);
};

export default App;

Now it’s time for our last feature — Select all:

// File name: App.tsx

import React, { useState } from "react";
import "./App.css";
import { DEFAULT_DATA } from "./DEFAULT_DATA";

const App = () => {
const [toggleOpen, setToggleOpen] = useState(false);
const [selectedItems, setSelectedItems] = useState<string[]>([]);

// <----- NEW CODE ----->
const isSelectAllOn = DEFAULT_DATA.length === selectedItems.length;

// <----- NEW CODE ----->
const handleSelectAll = () =>
setSelectedItems(isSelectAllOn ? [] : DEFAULT_DATA);

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const selectedItem = event.target.value;

let updatedItems = [...selectedItems];

if (selectedItems.includes(selectedItem)) {
updatedItems = selectedItems.filter((item) => item !== selectedItem);
} else {
updatedItems.push(selectedItem);
}

setSelectedItems(updatedItems);
};

const placeholderText = selectedItems.length
? `${selectedItems.length} ${
selectedItems.length === 1 ? "item" : "items"
} selected`
: "Choose at least one item";

return (
<div className="container">
<div
className="multiple-select"
onClick={() => setToggleOpen((toggle) => !toggle)}
>
<div className="placeholder">{placeholderText}</div>
<div
className={`icon ${toggleOpen ? "triangle-up" : "triangle-down"}`}
/>
</div>
{toggleOpen && (
<div className="dropdown">
<div>
{DEFAULT_DATA.map((d) => (
<div key={d} className="dropdown-item">
<input
type="checkbox"
id={d}
name={d}
value={d}
onChange={handleChange}
checked={selectedItems.includes(d)}
/>
<label htmlFor={d} className="label">
{d}
</label>
</div>
))}
</div>
</div>
)}
{/* NEW CODE */}
<div className="select-container">
<input
type="checkbox"
id="select-all"
name="select-all"
value="select-all"
onChange={handleSelectAll}
checked={isSelectAllOn}
/>
<label htmlFor="select-all" className="label">
Select all
</label>
</div>
</div>
);
};

export default App;

As you can see, here we have some new styles. Let’s add them to the CSS file:

/* File name: App.css */

/*

<- previous classes go here ->

*/

.select-container {
display: flex;
align-items: center;
margin: 10px 0;
}

.select-container > * {
cursor: pointer;
}

At the same time, the code above inside the App.tsx file is our final code for the multiple-select project. Below you can get an App.css file with all classes put together.

/* File name: App.css */

.container {
display: flex;
flex-direction: column;
margin: 50px;
}

.multiple-select {
width: 250px;
height: 40px;
border: 1px solid black;
display: flex;
align-items: center;
justify-content: space-evenly;
cursor: pointer;
}

.placeholder {
margin-left: 10px;
width: 100%;
}

.icon {
width: 0;
height: 0;
border-left: 7.5px solid transparent;
border-right: 7.5px solid transparent;
margin: 10px;
}

.triangle-up {
border-bottom: 12.5px solid #555;
}

.triangle-down {
border-top: 12.5px solid #555;
}

.dropdown {
width: 250px;
height: 100px;
border: 1px solid black;
display: flex;
flex-direction: column;
align-items: center;
padding: 10px 0;
overflow: scroll;
}

.dropdown-item > * {
cursor: pointer;
}

.label {
margin-left: 5px;
}

.select-container {
display: flex;
align-items: center;
margin: 10px 0;
}

.select-container > * {
cursor: pointer;
}

Thank you for reading, let’s get applause 👏 and see you next time!

--

--

ProgrammingCouple
ProgrammingCouple

Written by ProgrammingCouple

See programmingcouple.com with useful tools and statistics for Medium Members

No responses yet