Configuration pas à pas avec un exemple concret Next.js
Provider & Setup
DataTable est le point d'entrée unique. Vous passez la configuration et les actions en props, puis DataTable compose TableProvider en interne.
Breaking change : YaYaw Table ne crée plus de QueryClient interne. Vous devez fournir un client TanStack Query partagé via
QueryClientProvider(recommandé) ou passer explicitement une instancequeryClientpartagée.
Cette page montre un setup concret à copier, puis à adapter.
Setup pas à pas (Next.js App Router)
1. Ajouter NuqsAdapter dans votre root layout
Si vous voulez conserver le tri, les filtres, la pagination et la visibilité dans les paramètres d'URL, entourez l'app avec NuqsAdapter :
// app/layout.tsx
import { NuqsAdapter } from "nuqs/adapters/next/app";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="fr">
<body>
<NuqsAdapter>{children}</NuqsAdapter>
</body>
</html>
);
}2. Créer vos actions de table (minimum : list)
La table appelle vos actions pour les données et le CRUD. La seule action obligatoire est list.
// app/products/actions/products.ts
"use server";
type ListProductsParams = {
advancedFilters?: unknown[];
filters?: Record<string, unknown>;
limit?: number;
orderBy?: Record<string, "asc" | "desc">;
page?: number; // 1-based
search?: string;
};
const products = [
{ id: "1", name: "MacBook Pro", brand: "Apple", price: 2499, isActive: true },
{ id: "2", name: "ThinkPad X1", brand: "Lenovo", price: 1899, isActive: true },
{ id: "3", name: "XPS 13", brand: "Dell", price: 1599, isActive: false },
];
export async function listProducts(params: ListProductsParams) {
const { limit = 10, page = 1, search = "" } = params;
const filtered = products.filter((product) =>
product.name.toLowerCase().includes(search.toLowerCase())
);
const start = (page - 1) * limit;
const end = start + limit;
const paged = filtered.slice(start, end);
return {
data: paged,
meta: {
pageCount: Math.max(1, Math.ceil(filtered.length / limit)),
totalCount: filtered.length,
},
};
}
export async function createProduct(data: Record<string, unknown>) {
return { success: true, data };
}
export async function updateProduct(id: string, data: Record<string, unknown>) {
return { success: true, data: { id, ...data } };
}
export async function deleteProduct(id: string) {
return { success: true, data: { id } };
}3. Créer getTableConfig et getTableActions
Gardez le setup dans un seul fichier pour maintenir facilement chaque tableType :
// app/products/setup/table-config.ts
import {
createProduct,
deleteProduct,
listProducts,
updateProduct,
} from "../actions/products";
export const getTableConfig = (tableType: string) => {
if (tableType !== "products") {
return;
}
return {
defaultPageSize: 10,
enableColumnDnd: true,
enableColumnDragDropByDefault: false,
enableColumnFilters: true,
enableGrouping: false,
enableMultiRowSelection: true,
enablePagination: true,
enableRowDragDrop: false,
enableRowClickEdit: false,
enableRowSelection: true,
enableSorting: true,
pageSizeOptions: [10, 20, 50],
columns: {
definitions: [
{ id: "name", type: "text", header: "Name", enableSorting: true, enableColumnFilter: true },
{ id: "brand", type: "text", header: "Brand", enableSorting: true, enableColumnFilter: true },
{ id: "price", type: "number", header: "Price", enableSorting: true, enableColumnFilter: true },
{ id: "isActive", type: "boolean", header: "Active", enableSorting: true, enableColumnFilter: true },
{ id: "actions", type: "actions", header: "Actions", enableSorting: false, enableColumnFilter: false },
],
order: ["select", "name", "brand", "price", "isActive", "actions"],
visible: ["select", "name", "brand", "price", "isActive", "actions"],
mandatory: ["name"],
sort: [{ id: "name", desc: false }],
},
translations: {
namespace: "table.products",
keys: {
title: "Products",
description: "Manage your product catalog",
},
},
};
};
export const getTableActions = (tableType: string) => {
if (tableType !== "products") {
return;
}
return {
list: listProducts,
create: createProduct,
update: updateProduct,
delete: deleteProduct,
};
};4. Rendre DataTable dans un QueryClientProvider partagé
getTableConfig et getTableActions sont des fonctions, donc le composant qui les passe doit être un Client Component.
// app/products/page.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { DataTable } from "@/components/ui/yayaw-table";
import { getTableActions, getTableConfig } from "./setup/table-config";
const queryClient = new QueryClient();
export default function ProductsPage() {
return (
<QueryClientProvider client={queryClient}>
<DataTable
tableType="products"
getTableConfig={getTableConfig}
getTableActions={getTableActions}
title="Products"
description="Server-side pagination, filtering and sorting"
/>
</QueryClientProvider>
);
}5. Optionnel : ajouter getFormConfig pour les dialogs create/edit
Si vous voulez les formulaires create/edit/bulk-edit intégrés, fournissez getFormConfig :
// app/products/setup/form-config.ts
export const getFormConfig = (formType: string) => {
if (formType !== "products") {
return;
}
return {
id: "products",
fields: [
{ type: "text", name: "name", label: "Name", required: true },
{ type: "number", name: "price", label: "Price", required: true },
{ type: "switch", name: "isActive", label: "Active" },
],
};
};Puis passez-la :
<DataTable
tableType="products"
getTableConfig={getTableConfig}
getTableActions={getTableActions}
getFormConfig={getFormConfig}
/>Vérifications de configuration
Après les 4 étapes principales, votre table doit :
- Charger les données via
list(params). - Garder tri, filtres et pagination dans les paramètres URL.
- Appeler
create/update/deletevia les actions de ligne. - Utiliser vos colonnes/ordre/visibilité définis dans
getTableConfig.
getTableConfig
getTableConfig(tableType) doit retourner :
- Les clés de comportement de table (
enableRowSelection,enableRowClickEdit,enableSorting,enableColumnFilters,defaultPageSize,pageSizeOptions,export,bulkExport,actionsAsIcons). enableRowClickEditouvre le drawer d'édition au clic ligne et ne peut pas être combiné avecurlDisplayMode: "row-link"ni avec l'inline edit.- columns –
definitions(array de{ id, type, header, enableSorting?, enableColumnFilter? }),order,visible,mandatory,sort. - translations (optionnel) –
namespace,keyspour title/description.
Voir Configuration et Columns pour toutes les options.
Dans YaYaw Table, filtrage, pagination et tri sont server-side.
getTableActions
Retourne un objet avec les méthodes d'action. La table les appelle quand l'utilisateur trie, filtre, pagine ou exécute des actions CRUD/bulk.
- list – Obligatoire pour les données server-driven. Signature :
(params) => Promise<{ data, meta: { pageCount, totalCount } }>. - create, update, delete, duplicate – Optionnelles ; utilisées pour les actions de ligne et le comportement de masse par défaut.
- bulkDelete, bulkCopy, bulkUpdate – Optionnelles ; utilisées quand l'utilisateur lance des actions de masse.
Voir Actions et Server-side & Server Actions.
getFormConfig
Retourne la configuration de formulaire par type (ex. "products", "products-bulk"). Utilisée pour construire les dialogs create/edit et bulk edit. Structure : tableau de champs avec name, type, label, required, etc. Voir Formulaires et champs collection pour le modèle complet des champs, y compris les éditeurs natifs de tableaux.
Champs booléens : dans les formulaires catalogue, les booléens sont affichés en Switch par défaut (type: "switch" ou type: "checkbox"). Si vous voulez explicitement une checkbox, définissez variant: "checkbox".
{
type: "switch",
name: "isActive",
label: "Active",
}Champs collection
Utilisez type: "collection" quand une valeur de formulaire est un tableau d'objets et que les utilisateurs doivent ajouter, éditer, supprimer, réordonner et valider des items sans écrire un éditeur custom dans l'application consommatrice. La collection reste contrôlée par la valeur du formulaire : YaYaw Table écrit le prochain tableau via l'API de champ, sans état JSON opaque à synchroniser.
Le champ collection fournit nativement :
- Une vue structurée des items avec colonnes configurables.
- Un état vide clair.
- Un bouton d'ajout simple ou plusieurs actions d'ajout via
createActions. - Des actions éditer, supprimer, monter et descendre.
- Une dialog interne pour créer ou éditer un item.
- Des erreurs par ligne via
validateItem. - Des erreurs globales via
validateItems. - Le blocage du submit quand la collection est invalide.
import { defineFormConfig } from "@/components/ui/yayaw-table/components/forms";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
import { z } from "zod";
const FeatureSchema = z.object({
features: z.array(
z.object({
label: z.string().min(1),
enabled: z.boolean(),
})
),
});
export const featureForm = defineFormConfig({
id: "features",
schema: FeatureSchema,
defaultValues: {
features: [],
},
fields: [
{
type: "collection",
name: "features",
label: "Features",
description: "Manage the product feature list.",
addLabel: "Add feature",
itemLabel: "feature",
emptyLabel: "No features yet.",
columns: [
{ id: "label", header: "Label" },
{
id: "enabled",
header: "Enabled",
render: (item) => (item.enabled ? "Yes" : "No"),
},
],
createItem: () => ({ label: "", enabled: true }),
renderItemForm: ({ item, onChange, disabled }) => (
<div className="space-y-3">
<Input
disabled={disabled}
onChange={(event) =>
onChange({ ...item, label: event.target.value })
}
value={String(item.label ?? "")}
/>
<Switch
checked={item.enabled === true}
disabled={disabled}
onCheckedChange={(enabled) => onChange({ ...item, enabled })}
/>
</div>
),
validateItem: (item) =>
typeof item.label === "string" && item.label.length > 0
? []
: ["Label is required"],
validateItems: (items) =>
items.length > 0 ? [] : ["Add at least one feature"],
},
],
});validateItem marque les lignes invalides, validateItems affiche des erreurs globales, et les deux validateurs sont branchés au champ TanStack Form pour empêcher le submit. La validation Zod du formulaire reste active sur la même valeur.
Pour un JSON de menu, utilisez createActions afin de créer plusieurs formes d'items (link, group, themeToggle, languageToggle) et utilisez CollectionEditor dans le formulaire d'un groupe pour éditer group.items. La page Formulaires et champs collection détaille ce cas avec les collections imbriquées et le choix entre collection et custom.
Traductions de formulaire : le formulaire catalogue utilise le même objet DataTableTranslations que la table. Vous devez ajouter les clés form et value (ex. form.name, form.submit, value.string_placeholder) et utiliser labelKey / optionKeys dans vos champs pour résoudre labels et placeholders. Voir Translations Reference — Form translations pour l'exemple complet.
Props complètes (point d'entrée unique)
Toutes les props sont optionnelles sauf tableType.
| Prop | Type | Description |
|---|---|---|
tableType | string | Obligatoire. Identifiant de la table (ex. "products"). |
getTableConfig | (tableType: string) => Config | undefined | Retourne colonnes, options, tri par défaut, visibilité, clés de traduction. |
getTableActions | (tableType: string) => TableActions | undefined | Retourne list, create, update, delete, duplicate, bulkDelete, bulkCopy, bulkUpdate. |
getFormConfig | (formType: string) => FormConfig | undefined | Retourne les champs des formulaires create/edit/bulk. |
translations | DataTableTranslations | Surcharge les chaînes UI par défaut. |
locale | string | Locale pour les traductions (par défaut "en"). |
queryClient | QueryClient | Client explicite optionnel ; doit correspondre à l'instance du QueryClientProvider. |
title | string | Titre de la table au-dessus de la barre d'outils. |
description | string | Description courte sous le titre. |
enableToolbar | boolean | Affiche la barre d'outils (filtres, tri, colonnes, etc.). |
enableAdvancedFilters | boolean | Active le panneau de filtres avancés. |
columnTypeMapping | Record<string, 'text' | 'number' | 'date' | 'option' | 'multiOption'> | Mappe vos types backend vers les types internes de colonnes/filtres. |
onExport | (rows) => void | Promise<void> | Surcharge l'export de la barre d'outils (toutes les lignes filtrées). |
onBulkExport | (rows) => void | Promise<void> | Surcharge l'export bulk (lignes sélectionnées). |
onBulkEdit, onBulkDelete, onBulkCopy | (rows) => BulkActionResult | void | Surcharge des actions de masse ; contrat explicite recommandé pour un comportement déterministe. |
loadingOverlay | ReactNode | UI de chargement personnalisée. |
TitleComponent, DescriptionComponent | ComponentType | Composants personnalisés pour titre/description. |
Voir URL state (Nuqs) pour la synchronisation URL.
Erreurs de setup fréquentes
- Retourner un
pageen 0-based danslist; YaYaw Table attend un index 1-based. - Oublier
meta.pageCount/meta.totalCountdans la réponse delist. - Utiliser un
tableTypedifférent entreDataTable,getTableConfigetgetTableActions. - Rendre
DataTabledans un Server Component tout en passant des fonctions. - Oublier le
QueryClientProviderpartagé (erreur runtime QueryClient manquant).
Résumé
- DataTable est le composant unique ; passez
getTableConfig,getTableActions, etgetFormConfigsi nécessaire. - getTableConfig définit le comportement de la table et les colonnes.
- getTableActions branche vos Server Actions ou vos appels API.
- En App Router Next.js, utilisez NuqsAdapter, fournissez un QueryClientProvider partagé, et rendez
DataTabledepuis un Client Component si vous passez des fonctions.
Voir aussi :