How to build a Gmail like multi row checkbox selection in your NextJS/React app

Guide to building a multiple row checkbox selection in your ReactJS or NextJS app similar to Gmail.

How to build a Gmail like multi row checkbox selection in your NextJS/React app
Karthik Kamalakannan

Karthik Kamalakannan

Founder and CEO

Building apps for people is an experiment in human behavior. We strive to make iterations easier and fun. User experience is the integral part of an application, micro interactions play a vital role in forming a better user experience. In this article we’ll be looking at one such micro interaction, which is to build a smart and super easy-to-use multi select checkboxes.

Often NextJS is used to build dashboards and admin panels for its reactive approach. In many such apps there is an option to perform bulk operations on elements, like bulk deletion of posts, bulk updating of statuses and more. Applications like Gmail do it in an interactive way to make it easier to select multiple items without going one by one which would take the user an eternity to complete!

Pro tip: Learn how to manage cookie based auth for a multi-tenant NextJS application →

Setting up the project

In this article I’ll be creating a simple NextJS application. Personally I prefer NextJS as the primary framework for all my React apps as it comes with so many options.

So lets get started by creating the NextJS project,

Terminal window
yarn create next-app multi-select-app
cd multi-select-app

This will get you a project codebase with all the dependencies installed and ready for you. Let’s go and create a simple page for us to put lists and checkboxes.

Before we proceed, we need to have the data for our app; a list of something that we can act upon. In our case let it be the list of posts that we need to do some bulk operation on.

We’re going to create a simple JSON data for the purpose of this article, but this data can be source from anywhere like APIs or the database itself in your application.

[
{ "id": 1, "title": "Post 1", "body": "This is post 1" },
{ "id": 2, "title": "Post 2", "body": "This is post 2" },
{ "id": 3, "title": "Post 3", "body": "This is post 3" },
{ "id": 4, "title": "Post 4", "body": "This is post 4" },
{ "id": 5, "title": "Post 5", "body": "This is post 5" }
]

Now lets create the ReactJS component and loop the JSON data to form the list.

import POSTS from '../data/posts.json'
export default function Home() {
const renderPost = post => {
return (
<li key={post.id} className='m-1 flex items-center p-2 shadow'>
<input type='checkbox' className='' />
<h2 className='p-2'>{post.title}</h2>
</li>
)
}
return (
<div className='m-2 mt-5'>
<h1 className='text-2xl font-bold'>Posts</h1>
<ul className='list-none'>{POSTS.map(renderPost)}</ul>
</div>
)
}

With this you’ll get the basic skeleton of the application with a post list and a checkbox for each item in the list.

Now let us add the select functionality, for this we need to create a state which will hold all the selected item’s IDs and display it in a status section.

Initialize a new state selectedIDs

const [selectedIDs, setSelectedIDs] = useState([])

Add an onChange event for the checkboxes to update the selectedIds state every time we check a checkbox.

// Update the checkbox element with an onChange
const renderPost = post => {
return (
<li key={post.id} className='m-1 flex items-center p-2 shadow'>
<input
type='checkbox'
value={post.id}
onChange={handleChange}
checked={selectedIDs.includes(post.id.toString())}
/>
<h2 className='p-2'>{post.title}</h2>
</li>
)
}

In the handleChange function check if the current event is check or uncheck event and update the selectedIds state accordingly.

const handleChange = e => {
const checked = e.target.checked
const id = e.target.value
if (checked) {
setSelectedIDs([...selectedIDs, id])
} else {
setSelectedIDs(selectedIDs.filter(id => id !== id))
}
}

Lets add a status section on selected items counter to show how many posts have been selected.

<div className='rounded-md border-2 border-emerald-500 bg-emerald-100 p-1 text-sm'>
{selectedIDs.length} Posts are selected
</div>

Finally putting it all together we’ll get something like below as our post list with multi select.

import { useState } from 'react'
import POSTS from '../data/posts.json'
export default function Home() {
const [selectedIDs, setSelectedIDs] = useState([])
const handleChange = e => {
const checked = e.target.checked
if (checked) {
setSelectedIDs([...selectedIDs, e.target.value])
} else {
setSelectedIDs(selectedIDs.filter(id => id !== e.target.value))
}
}
const renderPost = post => {
return (
<li
key={post.id}
className='m-1 flex items-center rounded-md p-2 shadow-md'
>
<input type='checkbox' onChange={handleChange} />
<h2 className='p-2'>{post.title}</h2>
</li>
)
}
return (
<div className='m-2 mt-5'>
<div className='flex items-center justify-between'>
<h1 className='text-2xl font-bold'>Posts</h1>
<div className='rounded-md border-2 border-emerald-500 bg-emerald-100 p-1 text-sm'>
{selectedIDs.length} Posts are selected
</div>
</div>
<ul className='list-none'>{POSTS.map(renderPost)}</ul>
</div>
)
}

Now comes our secret recipe of the process which is to add ability to do multi select like how Gmail or file explorers do.

We should be able select multiple checkboxes by clicking and holding the Shift key on the keyboard.

There are two things we need to do here,

  1. When the check box is clicked we need to check if the Shift key is pressed
  2. Get the last checked item and currently checked item and all the ones in-between

After implementing these two the handleChange event will look like like this,

const handleChange = e => {
const checked = e.target.checked
const isMulti = e.nativeEvent.shiftKey
const id = e.target.value
if (checked) {
if (isMulti) {
const lastSelectedPost = selectedIDs[selectedIDs.length - 1]
const postIds = POSTS.map(post => post.id.toString())
const startIndex = postIds.indexOf(lastSelectedPost)
const lastIndex = postIds.indexOf(id)
const selectedPostIds = postIds.slice(startIndex, lastIndex + 1)
setSelectedIDs(_ids => [..._ids, ...selectedPostIds])
} else {
setSelectedIDs([...selectedIDs, id])
}
} else {
setSelectedIDs(selectedIDs.filter(id => id !== id))
}
}

So now when you select a checkbox and hold shift and select another checkbox below, the items in between will be selected and all of them will also be added to the selectedIds state. You can send this to the server to perform any bulk operation.

Here’s everything with all the bells and whistles implemented on featureOS’s dashboard.

https://youtu.be/IpfnTlOSQ0U