Engineering | 20 May, 2022

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

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 updation 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.app
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.

data/posts.json
[
  { "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.

index.js
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

index.js
const [selectedIDs, setSelectedIDs] = useState([])

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

index.js
// 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.

index.js
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.

index.js
<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.

index.js
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>
  )
}

And it looks something like this visually

Gmail like multi-select

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,

index.js
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 Hellonext's dashboard.

Last updated: August 3rd, 2022 at 3:55:27 AM GMT+0

Frequently Asked Questions (FAQ)

Need more help? Reach out to us
Hellonext Blogpost Author Profile

Varun Raj

Varun is one of the founders of Hellonext. He has helped companies like Google build large-scale developer communities to build strong developer relationships. He has build over 50 SaaS products in his career.

Ready to try Hellonext?

Superpower your customer feedback loop by creating your free account on Hellonext.

Create my account now

No credit card required • Free for 14 days • Setup in <2 mins