Full Example
This wires together everything from the previous sections. Each step links back to its guide page for details.
1. Define Your Table
Use createTableSchema to declare every column — display, filters, sorting, and row details — in one place. See Table Schema for the full API.
// table-schema.tsx
import {
col,
createTableSchema,
type InferTableType,
} from "@/lib/table-schema";
export const tableSchema = createTableSchema({
level: col.presets.logLevel(["error", "warn", "info", "debug"]),
date: col.presets.timestamp().label("Date").size(200).sheet(),
latency: col.presets
.duration("ms")
.label("Latency")
.sortable()
.size(110)
.sheet(),
status: col.presets.httpStatus().label("Status").size(60),
method: col.presets.httpMethod(["GET", "POST", "DELETE"]).size(69),
host: col.string().label("Host").size(125).sheet(),
});
export type ColumnSchema = InferTableType<typeof tableSchema.definition>;2. Create Filter Schema
Bridge the table schema to filter state with generateFilterSchema, then add non-column fields like sort and pagination. See State Management for details.
// schema.ts
import { createSchema, field } from "@/lib/store/schema";
import { generateFilterSchema } from "@/lib/table-schema";
export const filterSchema = createSchema({
...generateFilterSchema(tableSchema.definition).definition,
sort: field.sort(),
live: field.boolean().default(false),
size: field.number().default(40),
});
export type FilterState = typeof filterSchema._type;3. Set Up the API Route
Create an API handler with createDrizzleHandler that implements three-pass filtering, faceted search, and cursor pagination. See Database and Data Fetching for the underlying concepts.
// app/api/route.ts
import { createDrizzleHandler } from "@/lib/drizzle";
const handler = createDrizzleHandler({
db,
table: logs,
schema: tableSchema.definition,
columnMapping,
cursorColumn: "date",
});
export async function GET(req: NextRequest) {
const search = searchParamsCache.parse(
Object.fromEntries(req.nextUrl.searchParams),
);
const result = await handler.execute(search);
return Response.json(
SuperJSON.stringify({
data: result.data,
meta: {
totalRowCount: result.totalRowCount,
filterRowCount: result.filterRowCount,
facets: result.facets,
},
prevCursor: result.prevCursor,
nextCursor: result.nextCursor,
}),
);
}4. Render
Wire up useInfiniteQuery with DataTableInfinite and populate filter fields dynamically from server-side facet data. See Components for all available props.
// client.tsx
import { DataTableStoreProvider, useFilterState } from "@/lib/store";
import { useNuqsAdapter } from "@/lib/store/adapters/nuqs";
import {
generateColumns,
generateFilterFields,
generateSheetFields,
} from "@/lib/table-schema";
import { useInfiniteQuery } from "@tanstack/react-query";
const columns = generateColumns<ColumnSchema>(tableSchema.definition);
const filterFields = generateFilterFields<ColumnSchema>(tableSchema.definition);
const sheetFields = generateSheetFields<ColumnSchema>(tableSchema.definition);
export function Client() {
const adapter = useNuqsAdapter(filterSchema.definition, { id: "logs" });
return (
<DataTableStoreProvider adapter={adapter}>
<DataTableContent />
</DataTableStoreProvider>
);
}
function DataTableContent() {
const search = useFilterState<FilterState>();
const { data, isFetching, fetchNextPage, hasNextPage, refetch } =
useInfiniteQuery(dataOptions(search));
const flatData = React.useMemo(
() => data?.pages?.flatMap((page) => page.data ?? []) ?? [],
[data?.pages],
);
const { sort, live, size, ...filter } = search;
const defaultColumnFilters = Object.entries(filter)
.map(([key, value]) => ({ id: key, value }))
.filter(
({ value }) => value != null && !(Array.isArray(value) && !value.length),
);
return (
<DataTableInfinite
columns={columns}
data={flatData}
defaultColumnFilters={defaultColumnFilters}
defaultColumnSorting={sort ? [sort] : undefined}
filterFields={filterFields}
sheetFields={sheetFields}
schema={filterSchema.definition}
isFetching={isFetching}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
refetch={refetch}
/>
);
}Live Examples
Powered by OpenStatus
