import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
import { useCallback } from 'react'
interface DataTableProps<TData> {
data: TData[]
pageCount: number
pageIndex: number
pageSize: number
}
export function useServerDataTable<TData>({ data, pageCount, pageIndex, pageSize }: DataTableProps<TData>) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const createQueryString = useCallback((params: Record<string, string | number | null>) => {
const newParams = new URLSearchParams(searchParams.toString())
Object.entries(params).forEach(([key, value]) => {
if (value === null) newParams.delete(key)
else newParams.set(key, String(value))
})
return newParams.toString()
}, [searchParams])
return useReactTable({
data,
pageCount,
state: {
pagination: { pageIndex, pageSize },
},
manualPagination: true,
manualSorting: true,
manualFiltering: true,
onPaginationChange: (updater) => {
const nextState = typeof updater === 'function' ? updater({ pageIndex, pageSize }) : updater
router.push(`${pathname}?${createQueryString({ page: nextState.pageIndex + 1, limit: nextState.pageSize })}`)
},
getCoreRowModel: getCoreRowModel(),
})
}Salutare. Dacă ați încercat vreodată să încărcați peste 5.000 de rânduri într-un DataTable oferit de shadcn/ui folosind setările implicite client-side, probabil ați văzut browserul agățând sau chiar crapând. Am pățit-o recent pe un proiect B2B unde o masă de date cu 85.000 de facturi bloca tab-ul de Chrome vreo 3 secunde doar la inițializare.
Soluția e evidentă: server-side pagination, sorting și filtering. Totuși, integrarea dintre TanStack Table v8, Next.js App Router și shadcn/ui poate deveni rapid un meci frustrant dacă încerci să le sincronizezi manual prin usePathname și useRouter.
De ce abordarea client-side crapă la scale
Componenta DataTable din shadcn e minunată pentru prototipuri. Copiezi codul din docuri, trântești un getPaginationRowModel() și un getFilteredRowModel(), iar totul merge fluid... până când baza de date crește. Când ai tras 10MB de JSON prin API doar ca să afișezi 10 rânduri pe pagina 1, ai pierdut deja jocul de performanță.
Pe proiectul de care vă ziceam, trecerea de la client-side la server-side ne-a scăzut payload-ul de la 4.2MB la doar 12KB per request, iar timpul până la prima interacțiune a scăzut sub 50ms.
Sincronizarea stării în URL (Single Source of Truth)
Cea mai curată abordare pe care am găsit-o este să folosești URL-ul drept stare unică. Nicio stare locală de React pentru pagină sau căutare care să se piardă la refresh.
Trade-off-ul sincer? Dacă schimbi parametrii prin router.push() la fiecare apasare de tastă în search input, o să omori serverul cu request-uri. Trebuie neapărat să pui un debounce de 300-400ms pe input-ul de filtrare și să folosești useTransition pentru a menține interfața responsivă în timp ce Server Component-ul re-randază datele.
Principalele setări pe care trebuie să le activezi explicit în useReactTable sunt:
manualPagination: truemanualSorting: truemanualFiltering: truepageCount: totalPages(calculat pe server din DB)
Fără astea, TanStack Table va încerca în continuare să taie și să sorteze array-ul primit local, dând peste cap toate indecșii.
Cum gestionezi mutațiile fără UI glitch-uri
Când serverul procesează query-ul de Prisma sau Drizzle, e critic să-i trimiți pageIndex, pageSize, sortColumn, sortDirection și search. Aici apare o capcană: când utilizatorul caută ceva în search bar, trebuie să resetezi automat pageIndex la 0. Altfel, dacă ești pe pagina 15 și cauți ceva ce returnează doar 2 rezultate, tabelul va afișa o pagină goală.
Navigarea nativă din App Router funcționează grozav dacă îmbraci schimbările de parametri într-o funcție helper care actualizează query string-ul. Evită să stochezi array-ul de rezultate în useState local – lasă Server Component-ul părinte să facă fetch-ul și să paseze datele direct ca prop-uri.
Voi cum abordați tabelele mari în Next.js? Mergeți pe sincronizare pură în URL cu searchParams sau preferați un endpoint dedicat de API / Route Handler apelat cu TanStack Query?