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
GenericTrpcQueryProceduretype
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
inputobject should match the DataTable's expected pagination/filtering structure. - The return value must be an object with
data(array),totalCount(number), and optionallytotals(object for total row). - You can use
protectedProcedureorstaffProcedurefor authentication/authorization as needed.
Props
| Prop | Type | Description |
|---|---|---|
columns | ColumnDef<TData, any>[] | Table columns definition (see TanStack Table) |
queryProcedure | GenericTrpcQueryProcedure | tRPC query procedure for fetching data |
itemName | string | Name for items (used in UI text) |
customHeaderContent | React.ReactNode | Custom content for the table header |
globalFilterInputPlaceholder | string | Placeholder for global filter input |
noResultsMessage | string | Message when no results are found |
initialColumnVisibility | VisibilityState | Initial column visibility state |
initialColumnPinning | ColumnPinningState | Initial column pinning state |
meta | any | Custom meta data for table |
showTotalRow | boolean | Show total row at the bottom |
stickyHeader | boolean | Make header row sticky |
maxHeight | string | Max height for sticky header container |
serverSideFilterValues | Record<string, string[]> | Server-side filter values |
serverSideFilterCounts | Record<string, Record<string, number>> | Server-side filter counts per value |
tabConfigs | TabConfig[] | Tabbed table configs (see below) |
initialTabKey | string | Initial active tab key |
defaultPageSize | number | Default page size |
onRowClick | (row: Row<TData>) => void | Callback 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 interfaceTabConfig<TData, TQueryProc>: Tab configuration for tabbed tablesGenericTrpcQueryProcedure: 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.