Démarrage React
Une table React complète avec configuration typée et action de liste fonctionnelle.
Construisez une table produits en lecture seule avec recherche, tri, filtres et pagination. Cet exemple fonctionne dans une application React et utilise un petit adaptateur de liste en mémoire. Commencez par l’installation.
Structure du projet
products/
shared/products.ts
react/product-config.ts
react/product-actions.ts
react/products.tsx1. Définir les enregistrements
export interface Product extends Record<string, unknown> {
active: boolean;
id: string;
image: string;
name: string;
price: number;
/** Civil dates, inclusive at both ends: the window a batch is being restocked. */
restockFrom: string;
restockTo: string;
status: "draft" | "active" | "archived";
stock: number;
tags: string[];
updatedAt: string;
}
export const products: Product[] = [
{
id: "desk",
name: "Oak desk",
price: 420,
stock: 12,
status: "active",
active: true,
tags: ["office", "wood"],
image: "https://placehold.co/640x480/png?text=Oak+desk",
restockFrom: "2026-09-14",
restockTo: "2026-09-18",
updatedAt: "2026-09-01T09:00:00Z",
},
{
id: "lamp",
name: "Desk lamp",
price: 65,
stock: 0,
status: "draft",
active: false,
tags: ["office"],
image: "https://placehold.co/640x480/png?text=Desk+lamp",
restockFrom: "2026-09-17",
restockTo: "2026-09-24",
updatedAt: "2026-09-02T09:00:00Z",
},
{
id: "chair",
name: "Studio chair",
price: 180,
stock: 6,
status: "active",
active: true,
tags: ["office", "seating"],
image: "https://placehold.co/640x480/png?text=Studio+chair",
restockFrom: "2026-09-23",
restockTo: "2026-10-02",
updatedAt: "2026-09-03T09:00:00Z",
},
];2. Décrire les colonnes et les comportements
Les mutations et les vues sauvegardées sont désactivées pour cette première table. syncUrl: false garde l’exemple indépendant de l’URL.
import { defineTableConfig } from "@/components/ui/yayaw-table";
export const productConfig = defineTableConfig({
id: "products",
translations: { namespace: "products", keys: {} },
columns: {
definitions: [
{ id: "name", header: "Name", type: "text" },
{ id: "price", header: "Price", type: "number" },
{ id: "stock", header: "Stock", type: "number" },
{
id: "status",
header: "Status",
type: "select",
options: [
{ label: "Draft", value: "draft" },
{ label: "Active", value: "active" },
{ label: "Archived", value: "archived" },
],
},
],
order: ["name", "price", "stock", "status"],
visible: ["name", "price", "stock", "status"],
mandatory: ["name"],
sort: [{ id: "name", desc: false }],
},
table: {
allowCreate: false,
allowEdit: false,
allowDelete: false,
allowDuplicate: false,
allowBulkEdit: false,
allowBulkDelete: false,
enableRowSelection: false,
enableViews: false,
syncUrl: false,
defaultPageSize: 10,
},
});
export const getTableConfig = (tableType: string) =>
tableType === "products" ? productConfig : undefined;3. Implémenter l’action de liste
L’adaptateur applique la requête avant de découper la page. Ses données restent en mémoire : remplacez-le par une API HTTP ou une Server Action pour la persistance. Seul name participe à la recherche globale de cet exemple.
import type { TableActions } from "@/components/ui/yayaw-table/providers/table-provider";
import { products } from "../shared/products";
import { listRecords } from "./list-records";
export const productActions: TableActions = {
list: (input) => listRecords(products, input),
};
export const getTableActions = (tableType: string) =>
tableType === "products" ? productActions : undefined;Adaptateur de liste complet
import {
compatibleListParams,
matchesContractFilter,
normalizeFilterEnvelope,
recordValue,
} from "@/components/ui/yayaw-table/utils/table-contracts";
export function listRecords<T extends Record<string, unknown>>(
rows: T[],
input: unknown
) {
const params = compatibleListParams(recordValue(input));
const page = Number(params.page);
const limit = Number(params.limit);
const search = String(params.search).toLowerCase();
const filters = Object.entries(recordValue(params.filters));
const advanced = normalizeFilterEnvelope(params.advancedFilters).filters;
const matching = rows.filter((product) => {
const row: Record<string, unknown> = product;
const matchesSearch =
!search ||
["name", "reference", "price", "stock", "status", "active", "tags"].some(
(key) => {
const value = product[key];
return (
value !== null &&
value !== undefined &&
String(value).toLowerCase().includes(search)
);
}
);
const matchesColumns = filters.every(([id, value]) =>
matchesContractFilter(row[id], {
type: typeof row[id] === "number" ? "number" : "text",
operator: Array.isArray(value) ? "isAnyOf" : "contains",
values: Array.isArray(value) ? value : [value],
})
);
const matchesRule = (rule: Record<string, unknown>) =>
matchesContractFilter(row[String(rule.columnId)], rule);
const matchesAdvanced =
advanced.length === 0 ||
(params.advancedFilterJoin === "or"
? advanced.some(matchesRule)
: advanced.every(matchesRule));
return matchesSearch && matchesColumns && matchesAdvanced;
});
const sorting = Object.entries(recordValue(params.orderBy));
matching.sort((left, right) => {
for (const [id, direction] of sorting) {
const a = (left as Record<string, unknown>)[id];
const b = (right as Record<string, unknown>)[id];
const comparison =
typeof a === "number" && typeof b === "number"
? a - b
: String(a ?? "").localeCompare(String(b ?? ""));
if (comparison !== 0) {
return direction === "desc" ? -comparison : comparison;
}
}
return String(left.id).localeCompare(String(right.id));
});
return Promise.resolve({
// Model a server response: later store mutations must not alter cached rows.
data: structuredClone(matching.slice((page - 1) * limit, page * limit)),
meta: {
pageCount: Math.max(1, Math.ceil(matching.length / limit)),
totalCount: matching.length,
},
});
}4. Afficher la table
Gardez un QueryClient stable dans l’application. Cet exemple autonome le crée dans l’état du composant. Nuqs nécessite son adaptateur même lorsque l’écriture dans l’URL est désactivée, car les hooks URL restent montés.
Données de démonstration. Les modifications restent dans cet aperçu.
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NuqsAdapter } from "nuqs/adapters/react";
import { useState } from "react";
import { DataTable } from "@/components/ui/yayaw-table";
import type { ExamplePresentation } from "./presentation";
import { getTableActions } from "./product-actions";
import { getTableConfig } from "./product-config";
export default function Products(presentation: ExamplePresentation = {}) {
// Reuse the application's provider instead when it already owns a QueryClient.
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
<NuqsAdapter>
<DataTable
{...presentation}
getRowId={(row) => String(row.id)}
getTableActions={getTableActions}
getTableConfig={getTableConfig}
tableId="products-example"
tableType="products"
title={presentation.title ?? "Products"}
/>
</NuqsAdapter>
</QueryClientProvider>
);
}Next.js App Router
Dans Next.js, montez NuqsAdapter depuis nuqs/adapters/next/app dans le layout de l’application, à la place de l’adaptateur React autonome. Gardez les fonctions de catalogue dans un Client Component. Fournissez de véritables Server Actions à ce module client, ou appelez une API HTTP ; ne transmettez pas de fonctions serveur ordinaires à travers cette frontière.
Vérifier le résultat
Recherchez lamp, triez par Price et filtrez Status. La table doit afficher les lignes correspondantes et les bons totaux. Continuez avec les formulaires, les vues sauvegardées ou la connexion d’un backend.
Erreurs fréquentes
Utilisez le même
tableTypedans le composant et les deux fonctions de résolution.Fournissez un QueryClient et l’adaptateur Nuqs approprié.
Gardez les identifiants de ligne stables entre les pages.
Renvoyez les totaux du jeu filtré, pas uniquement ceux de la page courante.
Ajoutez un unique Toaster Sonner à l’application lorsque vous activez les mutations et les vues sauvegardées.
Sections du guide précédent
Provider & Setup
Consultez le guide actuel : concepts.
Setup pas à pas (Next.js App Router)
Consultez le guide actuel : concepts.
1. Ajouter NuqsAdapter dans votre root layout
Consultez le guide actuel : url-state.
2. Créer vos actions de table (minimum : list)
Consultez le guide actuel : actions.
3. Créer getTableConfig et getTableActions
Consultez le guide actuel : actions.
4. Rendre DataTable dans un QueryClientProvider partagé
Consultez le guide actuel : query-integration.
5. Optionnel : ajouter getFormConfig pour les dialogs create/edit
Consultez le guide actuel : forms.
Vérifications de configuration
Consultez le guide actuel : configuration.
getTableConfig
Consultez le guide actuel : configuration.
getTableActions
Consultez le guide actuel : actions.
getFormConfig
Consultez le guide actuel : forms.
Champs collection
Consultez le guide actuel : collections.
Props complètes (point d'entrée unique)
Consultez le guide actuel : datatable.
Erreurs de setup fréquentes
Consultez le guide actuel : concepts.
Résumé
Consultez le guide actuel : concepts.
Notifications de l’application
Consultez le guide actuel : notifications.