Reputation: 3971
I have clients table; I want to use Tanstack React Table because of its pagination, filtering... In one column I need to fetch more data about the client, (such as reports briefing about the client). I know that columns should be client component, but also I know that a client component can include a server component, so I thought maybe I can use a server component to fetch the extra data for each row. These are my files:
Dashboard.tsx:
import { columns } from '@/app/dashboard/components/columns';
import { DataTable } from '@/app/dashboard/components/data-table';
import { Client, ClientSchema, fetchClients } from '@/models/client';
// Simulate a database read for tasks.
export default async function Dashboard({ clients }: { clients: Client[] }) {
return (
<div className="">
<DataTable data={clients} columns={columns} />
</div>
);
}
data-table.tsx (from shadcn ui)
'use client';
import * as React from 'react';
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ui/table';
import { DataTablePagination } from './data-table-pagination';
import { DataTableToolbar } from './data-table-toolbar';
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) {
const [rowSelection, setRowSelection] = React.useState({});
const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>([]);
const [sorting, setSorting] = React.useState<SortingState>([]);
const table = useReactTable({
data,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
});
return (
<div className="space-y-4">
<DataTableToolbar table={table} />
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} />
</div>
);
}
columns.tsx
'use client';
import { ColumnDef, createColumnHelper } from '@tanstack/react-table';
import { Client } from '@/models/client';
import RowReports from './row-reports';
const columnHelper = createColumnHelper<Client>();
export const columns: ColumnDef<Client>[] = [
{
accessorKey: 'name',
cell: (props) => {
const row: any = props.row;
return (
<div className="flex space-x-2">
<span className="max-w-[200px] truncate font-medium">{row.original.name}</span>
</div>
);
},
},
columnHelper.display({
id: 'id',
cell: (props) => (
<div>
<RowReports clientId={props.row.original.id} />
</div>
),
}),
];
row-reports.tsx
'use server';
import { Client } from '@/models/client';
import { fetchInitialReport } from '@/models/initial-report';
async function RowReport({ clientId }: { clientId: string }) {
return <div>{clientId}</div>;
}
export default RowReport;
The issue is that I'm getting 1 error:
Unhandled Runtime Error Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding
'use client'
to a module that was originally written for the server.
and one warning in the dev tools console:
Warning: Cannot update a component (
Router
) while rendering a different component (proxy
). To locate the bad setState() call insideproxy
,
First question and most important: Is my architecture correct?
Should I use client side instead of server side to fetch row related extra data?
Can my approach be fixed?
Thanks
Upvotes: 1
Views: 2038
Reputation: 17652
To answer just the question in the title, there's no reason you can't use TanStack Table with React Server Components.
However, for the table itself to be interactive, it must be a client component, which can be included in a server component.
To get the benefit of server components, you can use a server component to fetch the data you want to show in the table to avoid a round-trip to fetch the data later. Optionally, TanStack Query can make this easier and it supports React Server Components - for details see Advanced Server Rendering - hopefully everything makes sense after studying this guide)
With the data fetched on the server, the table can be rendered on the server as well to load quickly and be hydrated later for interactivity (this is what SSR is).
You can also invoke a React Server Function from the table just as you would from anywhere else, or perhaps even pass server components into a client component's props and put them into the table (but I don't actually know enough to confirm).
Upvotes: 0
Reputation: 3213
"but also I know that a client component can include a server component"
This is not true, You cannot import a Server Component into a Client Component
You can only pass Server Components as a prop to a Client Component
Whenever you import Server Component into a Client Component, "ues server"
will be ignored and the component will be treated as a client component, hence, you see the error async/await is not yet supported in Client Components.
Since coulmns
is being used as a prop
not as a component, this will make it hard for row-reports
to be passed as children
, thus, it shouldn't be a server component.
is my architecture correct?
Yes, it's fine, you only need to remove two things from row-reports.tsx file, 'use server'
and async
keyword since it's causing the error and also you are not awaiting anything, so it's not required here.
Upvotes: 2