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

82 Views Asked by At

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

1

There are 1 best solutions below

1
MHD Alaa Alhaj On

"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.