• Blog
  • Documentation
  • Courses
  • Changelog
  • AI Starters
  • UI Kit
  • FAQ
  • Supamode
    New
  • Pricing

Launch your next SaaS in record time with Makerkit, a React SaaS Boilerplate for Next.js and Supabase.

Makerkit is a product of Makerkit Pte Ltd (registered in the Republic of Singapore)Company Registration No: 202407149CFor support or inquiries, please contact us

About
  • FAQ
  • Contact
  • Verify your Discord
  • Consultation
  • Open Source
  • Become an Affiliate
Product
  • Documentation
  • Blog
  • Changelog
  • UI Blocks
  • Figma UI Kit
  • AI SaaS Starters
License
  • Activate License
  • Upgrade License
  • Invite Member
Legal
  • Terms of License

A reusable Table component for React.js

Feb 17, 2023

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

react

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:

tsx
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:

tsx
<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:

tsx
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:

tsx
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!

Some other posts you might like...
Dec 25, 2024Updating Shadcn UI Components to React 19In React 19, the 'forwardRef' function was deprecated. This post will show you how to update your Shadcn UI components to work with React 19 and ensure future compatibility.
Dec 23, 2024Building Embeddable React Widgets: A Complete GuideLearn how to create embeddable React widgets for your web application. Inject your React widget into any website with ease.
Jul 9, 2024Building Multi-Step forms with React.jsIn this article, we explain how to build Multi-Step forms with Next.js and the library react-hook-form
Feb 17, 2023Pagination with React.js and SupabaseDiscover the best practices for paginating data using Supabase and React.js using the Supabase Postgres client