Skip to main content

Data Table Component

A flexible, type-safe, and highly configurable data table component for React/Next.js projects, built on top of @tanstack/react-table.

Features

  • TypeScript-first with strong type inference
  • Supports server-side data fetching (tRPC compatible)
  • Tabbed views with independent queries and columns
  • Column pinning, visibility, and sorting
  • Sticky headers and total row support
  • Customizable toolbar and header content
  • Pagination, filtering, and global search

Basic Usage

import { DataTable } from "@/components/data-table";
import { columns } from "./columns";

<DataTable
columns={columns}
queryProcedure={trpc.example.useQuery}
itemName="users"
/>;

Use Cases

1. Simple Table with Local Data

<DataTable
columns={columns}
queryProcedure={{
useQuery: () => ({
data: { data: localData, totalCount: localData.length },
isPending: false,
isFetching: false,
isError: false,
error: null,
}),
}}
itemName="products"
/>

2. Server-side Pagination and Filtering (tRPC)

<DataTable
columns={columns}
queryProcedure={trpc.products.list.useQuery}
itemName="products"
showTotalRow
stickyHeader
maxHeight="70vh"
/>

3. Tabbed Table with Different Data Sources

<DataTable
columns={columns}
tabConfigs={[
{
key: "all",
label: "All",
queryProcedure: trpc.products.all.useQuery,
columns,
},
{
key: "archived",
label: "Archived",
queryProcedure: trpc.products.archived.useQuery,
columns,
},
]}
initialTabKey="all"
/>

4. Custom Toolbar Content

<DataTable
columns={columns}
queryProcedure={trpc.orders.list.useQuery}
customHeaderContent={<button onClick={handleExport}>Export</button>}
/>

5. Column Pinning and Visibility

<DataTable
columns={columns}
queryProcedure={trpc.users.list.useQuery}
initialColumnPinning={{ left: ["name"], right: ["actions"] }}
initialColumnVisibility={{ email: false }}
/>

6. Row Click Navigation

<DataTable
columns={columns}
queryProcedure={trpc.users.list.useQuery}
rowNavigation={{
getViewPath: (row) => {
const user = row.original;
return `/users/${user.id}`;
},
}}
/>

How to Write a tRPC API for DataTable

To use DataTable with server-side data, your tRPC procedure must:

  • Accept pagination, sorting, and filtering parameters (e.g. skip, take, sorting, columnFilters, search)
  • Return an object with { data: TData[], totalCount: number, totals?: any }
  • Be compatible with the GenericTrpcQueryProcedure type

Example tRPC Procedure

import { z } from "zod";
import { createTRPCRouter, publicProcedure } from "@/server/api/trpc";

export const userRouter = createTRPCRouter({
list: publicProcedure
.input(
z.object({
skip: z.number().default(0),
take: z.number().default(25),
sorting: z
.array(z.object({ id: z.string(), desc: z.boolean() }))
.optional(),
columnFilters: z.any().optional(),
search: z.string().optional(),
}),
)
.query(async ({ input, ctx }) => {
// Fetch data from DB using input.skip, input.take, input.sorting, input.columnFilters, input.search
const [data, totalCount] = await ctx.db.user.findAndCount({
skip: input.skip,
take: input.take,
// ...apply sorting, filters, search
});
// Optionally, calculate totals for columns
const totals = {
/* ... */
};
return { data, totalCount, totals };
}),
});

Example Usage in DataTable

<DataTable
columns={columns}
queryProcedure={trpc.user.list.useQuery}
itemName="users"
/>

Notes

  • The input object should match the DataTable's expected pagination/filtering structure.
  • The return value must be an object with data (array), totalCount (number), and optionally totals (object for total row).
  • You can use protectedProcedure or staffProcedure for authentication/authorization as needed.

Props

PropTypeDescription
columnsColumnDef<TData, any>[]Table columns definition (see TanStack Table)
queryProcedureGenericTrpcQueryProceduretRPC query procedure for fetching data
itemNamestringName for items (used in UI text)
customHeaderContentReact.ReactNodeCustom content for the table header
globalFilterInputPlaceholderstringPlaceholder for global filter input
noResultsMessagestringMessage when no results are found
initialColumnVisibilityVisibilityStateInitial column visibility state
initialColumnPinningColumnPinningStateInitial column pinning state
metaanyCustom meta data for table
showTotalRowbooleanShow total row at the bottom
stickyHeaderbooleanMake header row sticky
maxHeightstringMax height for sticky header container
serverSideFilterValuesRecord<string, string[]>Server-side filter values
serverSideFilterCountsRecord<string, Record<string, number>>Server-side filter counts per value
tabConfigsTabConfig[]Tabbed table configs (see below)
initialTabKeystringInitial active tab key
defaultPageSizenumberDefault page size
onRowClick(row: Row<TData>) => voidCallback fired when a row is clicked

Example: Sticky Header, Pinned Columns, and Tabs

<DataTable
columns={columns}
stickyHeader={true}
maxHeight="60vh"
initialColumnPinning={{ left: ["name"], right: ["actions"] }}
showTotalRow={true}
tabConfigs={[
{
key: "all",
label: "All Users",
queryProcedure: trpc.users.all.useQuery,
columns,
},
{
key: "admins",
label: "Admins",
queryProcedure: trpc.users.admins.useQuery,
columns: adminColumns,
},
]}
initialTabKey="all"
/>

Column Definition Example

import { ColumnDef } from "@tanstack/react-table";

export const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: "Name",
cell: (info) => info.getValue(),
footer: "Total",
},
// ...other columns
];

TypeScript Types

  • DataTableProps<TData, TQueryProc>: Main props interface
  • TabConfig<TData, TQueryProc>: Tab configuration for tabbed tables
  • GenericTrpcQueryProcedure: tRPC query procedure shape

Constants

  • DEFAULT_PAGE_SIZE: Default page size (25)

Advanced: Custom Toolbar/Header

You can pass custom React nodes to customHeaderContent to render above the table, or extend the toolbar by editing data-table-toolbar.tsx.


File Structure

data-table/
├── columns/ # Column definitions (recommended)
├── constants.ts # Default constants
├── data-table-actions-dropdown.tsx # Example: row actions
├── data-table-configuration-skeleton.tsx
├── data-table-content.tsx
├── data-table-core.tsx
├── data-table-error.tsx
├── data-table-header-cell.tsx
├── data-table-pagination.tsx
├── data-table-skeleton.tsx
├── data-table-toolbar.tsx
├── formatters.ts # Utility formatters
├── index.tsx # Main DataTable component
├── types.ts # TypeScript types
├── use-data-table.ts # Main table logic/hook

See Also


For more advanced usage, see the code and comments in each file.