DataTable Reference
Props and usage reference for the unified DataTable component
DataTable Reference
The single entry-point component: you pass config and actions as props; it composes the provider, UI, and table logic internally.
import { DataTable } from "@/components/ui/yayaw-table";For a minimal working setup you need tableType, getTableConfig, and getTableActions. See Provider & Setup for the full list and Next.js layout (NuqsAdapter, QueryClientProvider).
If no QueryClient is available from context (and no explicit queryClient prop is provided), DataTable now throws an explicit runtime error.
Props
List of available props for configuring DataTable.
tableType
The type of table configuration to use. Used to resolve config and actions (e.g. getTableConfig("products"), getTableActions("products")).
<DataTable tableType="products" />Required: true
tableId
Stable table instance id for URL state, cache keys, selection, pagination, and toolbar state. Defaults to tableType.
Use it when one table instance displays several business models but should still share one URL/cache/selection surface.
Type: string
formType
Default form configuration type for create/edit forms. Defaults to tableType. Table config can still override it with form.createFormType, form.editFormType, or form.resolveEditFormType(row).
Type: string
getTableConfig
Function that returns table and column configuration for the given tableType. Required for the table to know columns, sort, visibility, and options. See Configuration and Setup.
Type: (tableType: string) => Config | undefined
getTableActions
Function that returns actions (list, create, update, delete, bulk, etc.) for the given tableType. list is required for server-driven data. Can be Server Actions in Next.js. See Actions and Server-side & Server Actions.
Type: (tableType: string) => TableActions | undefined
getFormConfig
Function that returns form field definitions for create/edit and bulk edit dialogs. Optional but needed for forms.
Type: (formType: string, ctx?: FormConfigContext) => FormConfig | undefined
ctx includes { mode, tableId, tableType, formType, row, initialData, values }, so form fields can change for edit rows or for current create-form values.
queryClient
Optional explicit TanStack Query client instance. Yayaw Table no longer creates one internally. In most apps you should provide a shared app-level QueryClientProvider and omit this prop.
Type: QueryClient
initialData
Rows rendered into the first client table state. Use this when a Server Component has already loaded the first page and you want the table to hydrate with useful data before TanStack Query performs its client refresh.
Type: Record<string, unknown>[] | Default: []
initialPageCount
Total page count that matches initialData. Provide it for server-paginated datasets so the first render keeps the correct pagination controls instead of assuming a single page.
Type: number | Default: 1 when initialData is present
initialRowCount
Total row count that matches initialData. Provide it when the server knows the full filtered total for the initial page.
Type: number | Default: initialData.length
className
Additional class names applied to the root wrapper.
Type: string | Default: undefined
loadingOverlay
Custom loading overlay to display while data is loading.
Type: React.ReactNode | Default: internal spinner overlay
<DataTable tableType="products" loadingOverlay={<MySpinner />} />onRowSelectionChange
Callback fired when selected rows change.
Type: (rows: Row<Record<string, unknown>>[]) => void | Default: undefined
activeRowId
Mark one row as active. Use this for master-detail tables where the table is the inventory surface and another panel renders the selected row.
Type: string | Default: undefined
getRowId
Return a stable row id for active-row highlighting, selection, and row metadata. If omitted, the table falls back to common id fields and the React Table row id.
Type: (row: Record<string, unknown>) => string | Default: undefined
onRowActivate
Callback fired when table.rowClickMode is set to 'activate' and a non-interactive row or card area is clicked.
Type: (row: Record<string, unknown>, event: React.MouseEvent) => void | Default: undefined
onRowClick
Callback fired by link-style row interactions, including row-link table clicks and Gallery link buttons. Use it to route through app navigation or analytics instead of relying on the browser default.
Type: (url: string, row: Record<string, unknown>, event: React.MouseEvent) => void | Default: undefined
emptyState
Per-instance empty/no-results state override. It is merged over table.emptyState from configuration.
Type: { show?: boolean; title?: string; description?: string } | Default: undefined
onBulkEdit
Handle bulk edit of selected rows.
Type: (rows: Row<Record<string, unknown>>[]) => BulkActionResult | void | Promise<BulkActionResult | void> | Default: uses provider actions
onBulkDelete
Handle bulk deletion of selected rows.
Type: (rows: Row<Record<string, unknown>>[]) => BulkActionResult | BulkDeleteExecutionOutcome | void | Promise<...> | Default: uses provider bulkDelete / delete
onBulkCopy
Handle bulk duplication of selected rows.
Type: (rows: Row<Record<string, unknown>>[]) => BulkActionResult | void | Promise<BulkActionResult | void> | Default: internal clipboard copy
onExport
Override toolbar export behavior. Called with all rows matching current search/filters/sort state. If provided, default CSV download is skipped.
Type: (rows: Record<string, unknown>[]) => void \| Promise<void> | Default: internal CSV export
onBulkExport
Override bulk export behavior. Called with selected rows. If provided, default CSV download is skipped.
Type: (rows: Row<Record<string, unknown>>[]) => void \| Promise<void> | Default: internal CSV export
customBulkActions
Inject custom actions in the selected-row bulk actions menu. Supports a static array or a callback receiving live selected-row context.
Type: BulkAction<TData>[] | ((ctx: BulkActionContext<TData>) => BulkAction<TData>[]) | Default: undefined
Custom actions render after the built-in export action and before the built-in delete action. A custom action can be disabled from the current selection context, can ask for confirmation, and can return a BulkActionResult to control menu closing, selection clearing, and user feedback.
type BulkActionContext<TData> = {
selectedRows: Row<TData>[];
selectedOriginalRows: TData[];
selectedCount: number;
};
type BulkAction<TData> = {
id: string;
label: string;
icon: ComponentType<{ className?: string; size?: number }>;
onClick: (ctx: BulkActionContext<TData>) =>
| BulkActionResult
| void
| Promise<BulkActionResult | void>;
disabled?: boolean | ((ctx: BulkActionContext<TData>) => boolean);
variant?: "default" | "destructive";
confirm?: {
title?: string | ((ctx: BulkActionContext<TData>) => string);
description?: string | ((ctx: BulkActionContext<TData>) => string);
confirmLabel?: string | ((ctx: BulkActionContext<TData>) => string);
cancelLabel?: string | ((ctx: BulkActionContext<TData>) => string);
};
};toolbarActions
Inject custom actions in the main toolbar. Supports a static array or a callback receiving live toolbar context.
Type: ToolbarAction[] | ((ctx: ToolbarActionContext) => ToolbarAction[]) | Default: undefined
toolbarActionsPlacement
Control where custom toolbar actions are rendered relative to built-in actions.
Type: "before-create" | "between-create-export" | "after-export" | Default: "between-create-export"
ToolbarAction and ToolbarActionContext
type ToolbarAction = {
id: string;
label: string;
icon?: ReactNode;
onClick: (ctx: ToolbarActionContext) => void | Promise<void>;
disabled?: boolean | ((ctx: ToolbarActionContext) => boolean);
loading?: boolean;
variant?: "default" | "outline" | "secondary" | "ghost" | "destructive";
requiresFooterCalculations?: boolean;
showInIconMode?: boolean; // default true
tooltip?: string;
};
type ToolbarActionContext = {
tableId: string;
actionsAsIcons: boolean;
isMobile: boolean;
isCreateEnabled: boolean;
isExportEnabled: boolean;
isExporting: boolean;
isFooterCalculationsEnabled: boolean;
hasListAction: boolean;
selectedRows: Row<Record<string, unknown>>[];
selectedOriginalRows: Record<string, unknown>[];
selectedRowIds: string[];
selectedCount: number;
tableActions?: TableActions;
};Multi-Model Table
A single table can keep one tableId while resolving table config and form config separately:
<DataTable
tableId="cms-entries"
tableType="content-index"
formType="content-entry"
getTableConfig={(tableType) => ({
...configs[tableType],
form: {
createFormType: "content-entry",
resolveEditFormType: (row) => `${row.modelId}-entry`,
},
})}
getFormConfig={(formType, ctx) =>
buildEntryForm({
formType,
modelId: String(ctx?.values?.modelId ?? ctx?.row?.modelId ?? ""),
})
}
customBulkActions={(ctx) => [
{
id: "publish-selected",
label: "Publish",
icon: Send,
disabled: ctx.selectedCount === 0,
onClick: async () => publishEntries(ctx.selectedOriginalRows),
},
{
id: "archive-selected",
label: "Archive",
icon: Archive,
variant: "destructive",
disabled: ctx.selectedCount === 0,
confirm: {
title: "Archive selected entries?",
description: `Archive ${ctx.selectedCount} selected entries.`,
},
onClick: async () => archiveEntries(ctx.selectedOriginalRows),
},
]}
/>Here cms-entries owns URL/cache/selection, content-index owns the table columns and filters, and each row can open its own edit form type.
Server-first initial data
For Next.js App Router pages, load the first page in a Server Component, then pass the rows and pagination metadata into a small Client Component that renders DataTable.
// app/products/page.tsx
import { listProducts } from "./actions/products";
import { ProductsTableClient } from "./products-table-client";
export default async function ProductsPage() {
const initial = await listProducts({ limit: 10, page: 1 });
return (
<ProductsTableClient
initialData={initial.data}
initialPageCount={initial.meta.pageCount}
initialRowCount={initial.meta.totalCount}
/>
);
}// app/products/products-table-client.tsx
"use client";
import { DataTable } from "@/components/ui/yayaw-table";
import { listProducts } from "./actions/products";
import { getTableConfig } from "./table-config";
export function ProductsTableClient({
initialData,
initialPageCount,
initialRowCount,
}: {
initialData: Record<string, unknown>[];
initialPageCount: number;
initialRowCount: number;
}) {
return (
<DataTable
getTableActions={() => ({ list: listProducts })}
getTableConfig={getTableConfig}
initialData={initialData}
initialPageCount={initialPageCount}
initialRowCount={initialRowCount}
tableType="products"
/>
);
}initialData is only the first cache value. Sorting, filtering, pagination, retry, and refresh still call your getTableActions().list action through TanStack Query, so authorization and data access stay in your server layer.
enableAdvancedFilters
Enable advanced filters UI when available in your configuration.
Type: boolean | Default: false
columnTypeMapping
Map dynamic backend types to internal column renderer types.
Type: Record<string, 'text' | 'number' | 'date' | 'option' | 'multiOption'> | Default: {}
Usage
<DataTable
tableType="products"
loadingOverlay={<MySpinner />}
onRowSelectionChange={(rows) => console.log(rows)}
onBulkDelete={(rows) => console.log("delete", rows.length)}
onExport={(rows) => console.log("export all", rows.length)}
onBulkExport={(rows) => console.log("export selected", rows.length)}
/>Custom action in text mode:
<DataTable
tableType="products"
toolbarActions={[
{
id: "recalculate-prices",
label: "Recalculate prices",
onClick: async () => {
await recalculatePrices();
},
variant: "secondary",
},
]}
/>Custom action in icon mode with tooltip and explicit placement:
<DataTable
tableType="products"
toolbarActions={(ctx) => [
{
id: "recalculate-prices",
label: "Recalculate prices",
tooltip: "Recalculate prices",
icon: <RefreshCw className="h-4 w-4" />,
requiresFooterCalculations: true,
disabled: () => !ctx.hasListAction || ctx.isExporting,
onClick: async () => {
await recalculatePrices();
},
},
]}
toolbarActionsPlacement="between-create-export"
/>Backward compatibility: if toolbarActions is omitted, toolbar behavior stays unchanged.
Use requiresFooterCalculations: true for custom actions that only make sense when table.enableCalculations is enabled. The table hides those actions while footer calculations are disabled.
Master-detail row activation:
"use client";
import { useState } from "react";
import { DataTable } from "@/components/ui/yayaw-table";
import { getTableActions, getTableConfig } from "./table-config";
export function ProductsInventoryTable() {
const [activeProductId, setActiveProductId] = useState<string>();
return (
<DataTable
activeRowId={activeProductId}
emptyState={{
title: "No products found",
description: "Try changing your search or filters.",
}}
getRowId={(row) => String(row.id)}
getTableActions={getTableActions}
getTableConfig={(tableType) => {
const config = getTableConfig(tableType);
return {
...config,
table: {
...config?.table,
layoutPreset: "preview",
rowClickMode: "activate",
},
};
}}
onRowActivate={(row) => {
setActiveProductId(String(row.id));
}}
tableType="products"
/>
);
}See also: