A reusable Table component for React.js

How to build a reusable Table component for React.js using Tanstack and Tailwind CSS.

The Tanstack Table component is by far Makerkit's recommended component for displaying tabular data. Being a lower-level component, it is not as opinionated as the other components and can be used in a variety of ways. It is extremely flexible, which means it needs more boilerplate code to use.

In this snippet, we will build a Table component that can be used to display tabular data. We will use Tailwind CSS for styling and the Tanstack Table component for the table itself.

The goal is to make it simpler to display tabular data in React.js, by reducing the boilerplate and keeping the code DRY.

This table component can:

  1. Display tabular data
  2. Display sub-components for each row
  3. Paginate the data

Let's get started!

The component's props

The Table component will accept the following props:

import type {
ColumnDef,
Row,
PaginationState,
} from '@tanstack/react-table';
interface ReactTableProps<T extends object> {
data: T[];
columns: ColumnDef<T>[];
renderSubComponent?: (props: { row: Row<T> }) => React.ReactElement;
pageIndex?: number;
pageSize?: number;
pageCount?: number;
onPaginationChange?: (pagination: PaginationState) => void;
className?: string;
}

The simplest version of your table will look like this:

<Table data={data} columns={columns} />

The Table component

Let's start by creating the Table component. We will use the Tanstack Table component for the table itself. We will also use Tailwind CSS for styling.

Third-party packages

Additionally, we will use the following packages:

  • classnames for conditionally applying CSS classes
  • @heroicons/react for the pagination icons
  • a few internal Makerkit components such as IconButton.

Treat these as details and feel free to replace these with your own components.

The React.js table component

Now, let's create the React.js table component. Below is the full code for the component:

import { Fragment, useEffect, useState } from 'react';
import {
getCoreRowModel,
useReactTable,
flexRender,
} from '@tanstack/react-table';
import type {
ColumnDef,
Row,
Table as ReactTable,
PaginationState,
} from '@tanstack/react-table';
import classNames from 'classnames';
import IconButton from '~/core/ui/IconButton';
import {
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from '@heroicons/react/24/outline';
interface ReactTableProps<T extends object> {
data: T[];
columns: ColumnDef<T>[];
renderSubComponent?: (props: { row: Row<T> }) => React.ReactElement;
pageIndex?: number;
pageSize?: number;
pageCount?: number;
onPaginationChange?: (pagination: PaginationState) => void;
className?: string;
}
function Table<T extends object>({
data,
columns,
renderSubComponent,
pageIndex,
pageSize,
pageCount,
onPaginationChange,
className,
}: ReactTableProps<T>) {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: pageIndex ?? 0,
pageSize: pageSize ?? 15,
});
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
manualPagination: true,
pageCount,
state: {
pagination,
},
onPaginationChange: setPagination,
});
useEffect(() => {
if (onPaginationChange) {
onPaginationChange(pagination);
}
}, [pagination, onPaginationChange]);
return (
<div className="flex flex-col">
<div className="overflow-x-auto">
<div className="inline-block min-w-full py-1">
<div className="overflow-hidden p-1">
<table className={classNames(`Table min-w-full`, className)}>
<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>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<Fragment key={row.id}>
<tr
className={classNames({
'bg-gray-50 transition-colors dark:bg-black-300':
row.getIsExpanded(),
})}
>
{row.getVisibleCells().map((cell) => (
<td
style={{
width: cell.column.getSize(),
}}
key={cell.id}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
))}
</tr>
{renderSubComponent ? (
<tr key={row.id + '-expanded'}>
<td colSpan={columns.length}>
{renderSubComponent({ row })}
</td>
</tr>
) : null}
</Fragment>
))}
</tbody>
</table>
</div>
<Pagination table={table} />
</div>
</div>
</div>
);
}
function Pagination<T>({
table,
}: React.PropsWithChildren<{
table: ReactTable<T>;
}>) {
return (
<div className="flex items-center gap-2">
<IconButton
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<ChevronDoubleLeftIcon className={'h-5'} />
</IconButton>
<IconButton
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeftIcon className={'h-5'} />
</IconButton>
<IconButton
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRightIcon className={'h-5'} />
</IconButton>
<IconButton
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<ChevronDoubleRightIcon className={'h-5'} />
</IconButton>
<span className="flex items-center gap-1 text-sm">
<div>Page</div>
<strong>
{table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
</strong>
</span>
</div>
);
}
export default Table;

Usage

Now, let's use the table component in our app. Assuming you want to use the table on a page, you can do the following:

function App() {
const data = [
{
id: 1,
name: 'John Doe',
email: ''
},
];
const columns = [
{
id: 'id',
header: 'Id',
accessorKey: 'id',
cell: (ctx) => ctx.getValue(),
},
{
id: 'name',
header: 'Name',
accessorKey: 'name',
cell: (ctx) => ctx.getValue(),
},
{
id: 'email',
header: 'Email',
cell: (ctx) => {
const { email } = ctx.row.original;
return <span className='font-bold'>{email}</span>
},
},
];
return <Table data={data} columns={columns} />
}

The above is a very simple example of how to use the table component. To see a more complex example, check out our article for paginating data with React.js and Supabase.

Conclusion

In this article, we have seen how to create a table component using React.js and Tanstack. The component above is very basic and can be extended to support more features, but can be used as a starting point for your own table component.

If you have any questions, feel free to join the Discord server!