import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
export function useDataTableParams() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const setParam = useCallback(
(name: string, value: string | null) => {
const params = new URLSearchParams(searchParams.toString());
if (value === null || value === "") {
params.delete(name);
} else {
params.set(name, value);
}
// Resetăm pagina la 1 dacă se schimbă filtrele sau sortarea
if (name !== "page") params.set("page", "1");
router.push(`${pathname}?${params.toString()}`, { scroll: false });
},
[searchParams, pathname, router]
);
return { searchParams, setParam };
}Am văzut prea multe proiecte de Next.js unde lumea trântește TanStack Table direct pe client și încarcă 50.000 de rânduri în memorie doar ca să folosească sortarea nativă. La un dashboard de logistică cu ~80k de comenzi pe care l-am refăcut recent, abordarea asta a prăjit browser-ul utilizatorilor și crăpa constant memoria pe mobil. Soluția sănătoasă e să arunci toată starea (pagina, sortarea, filtrele) în URL-ul din App Router și să faci fetch doar pentru ce afișezi pe ecran.
De ce e periculos stocarea stării în React local
Când instalezi shadcn/ui DataTable, exemplele din documentație vin implicit cu useState pentru sorting, columnFilters și pagination. E genial pentru un tabel mic de 50 de utilizatori dintr-un panou de admin intern.
Problema apare când crește baza de date. Dacă păstrezi starea în client, pierzi instant trei lucruri critice: URL-ul nu mai poate fi dat share unui coleg cu filtrele deja aplicate, butonul de back din browser e complet inutilizabil, iar serverul stă degeaba în timp ce clientul descarcă un payload gigant de JSON.
Arhitectura curată: URL ca Single Source of Truth
Secretul e să decuplezi TanStack Table de la gestionarea internă a stării. Pui manualPagination: true, manualSorting: true și manualFiltering: true în opțiunile de la useReactTable. Asta îi spune librăriei: "Hey, nu mai calcula tu paginarea, eu îți dau doar slice-ul curent de 10-20 de rânduri din DB".
În loc de useState, citești searchParams din Server Component-ul paginii și trimiți datele gata filtrate din Prisma, Drizzle sau ce ORM folosești. Când utilizatorul schimbă pagina sau sortează o coloană, modifici URL-ul folosind router.push cu { scroll: false } sau o librărie precum nuqs.
Acest lucru declanșează un re-render de Server Component curat, rapid și predictibil. Ai zero stări duplicate și un URL care descrie exact ceea ce vede omul pe ecran.
Trade-off-uri și ce te va enerva la implementare
Să fim sinceri, nu e totul numai lapte și miere când treci totul pe server:
- Latency la inputul de search: Dacă faci fetch pe server la fiecare literă tastată în câmpul de căutare, o să omori baza de date. Trebuie neapărat să pui un debounce de cel puțin 300ms-400ms înainte să actualizezi URL-ul.
- Boilerplate de mapare: Trebuie să scrii cod de legătură. Formatul TanStack (
[{ id: 'createdAt', desc: true }]) trebuie tradus manual în formatul așteptat de ORM-ul tău (orderBy: { createdAt: 'desc' }). - UI Flickering: Dacă nu folosești
React.useTransitioncând apelezi modificarea de URL, interfața poate părea agățată când serverul procesează query-ul.
Cum gestionați voi sincronizarea asta în proiectele mari? Preferați nuqs pentru query params sau vă scrieți voi hook-uri custom peste useSearchParams?