The Complete React Table Click and Row Selection Tutorial

Adding click handlers to tables, headers, rows, and cells in React requires knowledge of click handlers and JavaScript. Setting a “selected” style on clicked rows adds a CSS angle. In this tutorial we will explore to code to create this functionality.

Here’s the table we will build in a React app using simple React components. It’s the same as building a table with pure HTML, but we have access to nice react tools like useState.

React Table Row Click and Row Selection
React Table Row Click and Row Selection

React Table With Clickable Row

According to my keyword research, rows are the most commonly clicked element in a table. However, to demonstrate how row click works, we have to see the JSX code for the full table:

    <div style={{ marginTop: 24, marginLeft: 12 }}>
      <table>
        <thead>
          <tr>
            <th>#</th>
            <th>Street Address</th>
            <th>Zip Code</th>
            <th>City</th>
            <th>State</th>
          </tr>
        </thead>
        <tbody>
          {addresses.map((address, index) => {
            return (
              <tr key={index} onClick={() => console.log(index)}>
                <td>{address.id}</td>
                <td>{address.address}</td>
                <td>{address.zip}</td>
                <td>{address.city}</td>
                <td>{address.state}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>

In this tutorial I generate each row identically by iterating through the array of data. The only difference in each row is the actual data values.

The table row tr element has the onClick value. In the code above the onClick is only being passed a simple handler that logs the data index (which is the row index).

In the next section I’ll add a complex handler that controls row selection.

React Table Row Selection

If we want to give a visual cue to the user of which row was selected, we need to add a class to the row after a click and remove the class when a different row is clicked.

The first thing this requires is a state variable:

export default function SimpleReactTable() {
  //new state value:
  const [selectedRow, setSelectedRow] = React.useState(-1);

I set the default value to -1. Next we will add an updated click handle to the table rows that updates the selectedRow state value:

<tr key={index} onClick={() => setSelectedRow(address.id)} />

Now the state value tracks which row was most recently clicked. Next we will add logic that adds or removes a class from the row if the state value is equal to the row’s index:

className={"clickable-row ".concat(selectedRow === address.id ? "selected" : "")}

//css
.selected, .selected:hover {
  background-color: aqua;
}

With the code above, the clickable-row class is always applied to the row. There is a space after it, and then either an empty string is concatenated or "selected" is concatenated.

I added a hover value for the selected class so that if unselected rows have a hover value, the selected row still keeps its proper color when it is hovered.

React Table Row Hover

Adding hover styling to a React table component requires only a simple selector. I already have a class called clickable-row applied to every tr, so I created a selector that applies a hover pseudo class to it.

.clickable-row:hover {
  background-color: aquamarine;
}

Here’s an alternative selector that targets all tr elements and adds a hover pseudo class. Use this if you don’t have a class applied to all tr elements.

tr:hover {
  background-color: aquamarine;
}

React Table Click

The table component onClick handler is tricky to use because clicks anywhere on the table will bubble up to the table, so you risk having two different events fire.

However, if you do want a click handler on the table then take a look at this example:

<table onClick={() => { console.log(addresses[0].city) }}>

Here I log the city value of the first element in the addresses array any time the table is clicked. It doesn’t matter where the table is clicked. If the table header does not have a click handler but gets clicked, then the table onClick will fire. If the row has a handler and is clicked, the row onClick and the table onClick will both fire.

I’ll show how to stop this bubbling in the next section.

React Table Header Click

The thead component also supports onClick. Here’s an example:

<thead onClick={(e) => { console.log(addresses[0].state); e.stopPropagation() }}>

In this code I am logging the state value of the first element of the addresses array. Then I call e.stopPropogation() to keep the event from bubbling to the table element (or any higher at all).

The event did not need TypeScript typing in this case because it was implied to the compiler. If you are curious what the typing should be, here’s a screenshot from my VS Code intellisense:

React table header onClick event typing
React table header onClick event typing

React Table Cell Click

I expected table cells to be given click handlers often. However, my keyword research showed cell click is not searched for very often. The td onClick is similar to thead or table:

<td onClick={() => console.log(`Cell ${address.id}A was clicked!`)}>{address.id}</td>

In this case, I simply log the row data’s id field. This click will bubble up to the table level because I did not use the stopPropagation function.

MUI provides a table with more robust styling if you don’t want to start styling a table from scratch. The Bootstrap table is also a good pre-styled option.

Resources

Full code for this tutorial:

//CSS
table {
  border: 2px solid teal;
}

tr {
  border: 0px;
  border-bottom: 2px solid teal;
}

.clickable-row {
  cursor: pointer;
  height: 40px;
}

.clickable-row:hover {
  background-color: aquamarine;
}

.selected, .selected:hover {
  background-color: aqua;
}

//JSX
import React from "react";
import { faker } from "@faker-js/faker";
import "./SimpleReactTable.css";

const addresses: {
  [key: string]: any;
}[] = [];

for (let i = 0; i < 10; i++) {
  addresses.push({
    id: i + 1,
    address: `${faker.address.streetAddress()} ${faker.address.secondaryAddress()}`,
    zip: faker.address.zipCode(),
    city: faker.address.city(),
    state: faker.address.state()
  });
}

export default function SimpleReactTable() {
  const [selectedRow, setSelectedRow] = React.useState(-1);
  return (
    <div style={{ marginTop: 24, marginLeft: 12 }}>
      <table onClick={() => { console.log(addresses[0].city) }}>
        <thead onClick={(e) => { console.log(addresses[0].state); e.stopPropagation() }}>
          <tr>
            <th>#</th>
            <th>Street Address</th>
            <th>Zip Code</th>
            <th>City</th>
            <th>State</th>
          </tr>
        </thead>
        <tbody>
          {addresses.map((address, index) => {
            return (
              <tr key={index} onClick={() => setSelectedRow(address.id)} className={"clickable-row ".concat(selectedRow === address.id ? "selected" : "")}>
                <td onClick={() => console.log(`Cell ${address.id}A was clicked!`)}>{address.id}</td>
                <td>{address.address}</td>
                <td>{address.zip}</td>
                <td>{address.city}</td>
                <td>{address.state}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}
Share this post:

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.