Adding Meatballs Menu To React-Table Rows
source link: https://www.codejourney.net/adding-meatballs-menu-to-react-table-rows/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Meatballs menu (⋯), also called three horizontal dots menu, is a great way of providing contextual options for grid rows. In this article, I will show you how to add the meatballs menu to a table built with @tanstack/react-table.
After reading this article, you will know how to add such a menu to your React app. The end result will look as in the highlighted picture of this article
Creating a table with row selection support
First, let’s define a type of data that we want to display. For our example, we will display a list of Car
s:
export type Car = {
id: string;
brand: string;
model: string;
productionYear: number;
isAvailable: boolean;
};
Next, we create a new component called CarsTable
, responsible for rendering the table. We will use @tanstack/react-table for the table behavior and react-bootstrap for UI elements.
I implemented the CarsTable
component in quite a standard way according to react-table docs, so I won’t copy-paste it here. You can check the whole component’s code here. What’s interesting is that I added support for row selection in a way that makes our table a controlled React component:
type CarsTableProps = {
selectedCar: Car | null;
onCarSelected: (car: Car) => void;
};
export const CarsTable = (props: CarsTableProps) => {
return (
// ...
<tbody>
{table.getRowModel().rows.map((row) => {
const isActive = row.original.id === props.selectedCar?.id;
return (
<tr
key={row.id}
style={isActive === true ? { backgroundColor: "#3a7a11" } : undefined}
onClick={() => {
props.onCarSelected(row.original);
}}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
);
})}
</tbody>
// ...
)
}
As you can see, selectedCar
and onCarSelected
are managed from outside. Line 14 shows how the row color gets changed for the currently selected car. I recently had to deal with such a case in one of my projects.
So far, so good. This is how it looks, populated with sample data:
Adding meatballs menu
Ok, we have a table. Now we want to add the meatballs menu. We need a three dots icon and a dropdown menu to open on clicking it.
After quickly going through the react-bootstrap docs, let’s create a new component for that:
import { Dropdown } from "react-bootstrap";
import { Car } from "../types/Car";
import CustomDivToggle from "./CustomDivToggle";
import { BsThreeDots } from "react-icons/bs";
export const CarRowContextMenu = ({ carRow }: { carRow: Car }) => {
return (
<Dropdown key={carRow.id}>
<Dropdown.Toggle as={CustomDivToggle} style={{ cursor: "pointer" }}>
<BsThreeDots />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item>Option 1</Dropdown.Item>
<Dropdown.Item>Option 2</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
};
As we want our dropdown toggle to have custom style, I had to provide CustomDivToggle
as a custom dropdown component. It’s nothing very interesting, but you can check its implementation here
Next, as we’d like our meatballs menu to be an additional column in the grid, it seems natural to use react-table
‘s display column. Let’s try adding it:
columnHelper.display({
id: "context-menu",
cell: (cellContext) => {
const row = cellContext.row.original;
return <CarRowContextMenu carRow={row} />;
},
}),
It looks we have it:
However, after clicking through it for a while, it seems we have an issue. The toggle only opens on every 2nd click:
Why is that? The reason is our controlled CarsTable
component. On clicking a new row, the change event occurs, which triggers the re-render of the CarsTable
component (because selectedCar
actually changes). It makes react-table
re-render the table, with the meatballs menu in its default state (collapsed). On clicking the menu in the same row again, the change event occurs, but the selectedCar
does not actually change, which does not trigger the re-render. Initially, it took me a while to figure that out
Fixing double-click issue
In our case, a fix for the double click issue is quite simple. Instead of adding the column with the menu using columnHelper.display()
function from react-table
, we can render it manually. To do that, we should simply add a new <td>
to each row of the table:
<tbody>
{table.getRowModel().rows.map((row) => {
const isActive = row.original.id === props.selectedCar?.id;
return (
<tr
key={row.id}
style={isActive === true ? { backgroundColor: "#3a7a11" } : undefined}
onClick={() => {
props.onCarSelected(row.original);
}}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
<td>
<CarRowContextMenu carRow={row.original} />
</td>
</tr>
);
})}
</tbody>
Additionally, to make it work, we need an additional table’s header placeholder:
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
{/* placeholder header for context menu */}
<th></th>
</tr>
))}
</thead>
That’s it! Our `react-table` table with row selection support and the meatballs menu works like a charm now:
Meatballs menu with react-table – source code
You can find the complete source code here. I hope you find it useful!
Related Posts
- Write Test Progress To The Console With NUnit
I recently needed to write test progress to the console with NUnit. The task we…
- SQLite-Net Extensions – one-to-one relationships
In this second short post from SQLite-Net Extensions series, we're going to see how to…
- SQLite-Net Extensions – one-to-many relationships
In the 3rd post from SQLite-Net Extensions series we are covering the last type of relationship…
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK