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 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 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>
);
}