Aladdin Mhemed
Aladdin Mhemed

Reputation: 3971

In Next.js 14 can i use server component in Tanstack React Table?

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 inside proxy,

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

Answers (2)

Alexander Taylor
Alexander Taylor

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

MHD Alaa Alhaj
MHD Alaa Alhaj

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

Related Questions