Server-side & Server Actions
Utiliser les Server Actions pour list, create, update, delete et opérations bulk
Server-side & Server Actions
La table est conçue pour fonctionner avec des données server-side : tri, filtrage et pagination sont envoyés à votre backend, et les opérations CRUD/bulk s'exécutent côté serveur. Dans Next.js, l'approche recommandée pour cela est d'utiliser les Server Actions.
Comment ça marche
- État dans l'URL – Tri, filtres, pagination et visibilité des colonnes sont stockés dans l'URL (via Nuqs). Le client lit cet état et appelle votre couche de données avec les mêmes paramètres.
- getTableActions(tableType) – Vous retournez un objet dont les méthodes sont vos Server Actions (ou n'importe quelles fonctions async qui appellent votre API).
- list(params) – Appelée avec
{ filters, advancedFilters, limit, page (1-based), orderBy, search }. Votre action s'exécute côté serveur et retourne{ data, meta: { pageCount, totalCount } }. - create, update, delete, duplicate, bulkDelete, bulkCopy, bulkUpdate – Même principe : la table appelle la fonction que vous fournissez ; vous l'implémentez en Server Action ou en appel API.
Donc la librairie ne récupère pas les données elle-même ; elle appelle ce que vous passez à getTableActions. Si ce sont des Server Actions, tout s'exécute côté serveur.
Exemple Next.js avec Server Actions
1. Module serveur (optionnel mais recommandé)
Conservez votre logique de données dans un module server-only (par ex. lib/products-server.ts) : list avec filtre/tri/pagination, create, update, delete, opérations bulk. Ce fichier doit être importé uniquement depuis des Server Actions ou d'autres modules serveur.
// app/example/lib/products-server.ts
import { products as initialProducts } from "../data";
const productsStore = [...initialProducts];
export async function listProducts(params: {
page?: number;
limit?: number;
filters?: Record<string, unknown>;
advancedFilters?: unknown[];
orderBy?: Record<string, "asc" | "desc">;
search?: string;
}) {
const { page = 1, limit = 10, filters = {}, orderBy = {}, search = "" } = params;
// Filter, sort, paginate productsStore...
return { data: pageData, meta: { pageCount, totalCount } };
}
export async function createProduct(data: Record<string, unknown>) {
// Insert into productsStore or DB
return { success: true, data: newProduct };
}
export async function updateProduct(id: string, data: Record<string, unknown>) {
// Update and return
return { success: true, data: updated };
}
export async function deleteProduct(id: string) {
return { success: true };
}
export async function bulkDeleteProducts(ids: string[]) { /* ... */ }
export async function bulkCopyProducts(ids: string[]) { /* ... */ }
export async function bulkUpdateProducts(ids: string[], updateData: unknown) { /* ... */ }2. Fichier Server Actions
Créez un fichier avec "use server" qui ré-expose ces fonctions (ou appelle votre API). Ce sont ces fonctions que vous passez à la table.
// app/example/actions/products.ts
"use server";
import {
listProducts as listProductsImpl,
createProduct as createProductImpl,
updateProduct as updateProductImpl,
deleteProduct as deleteProductImpl,
bulkDeleteProducts,
bulkCopyProducts,
bulkUpdateProducts,
} from "../lib/products-server";
export async function listProducts(params: Parameters<typeof listProductsImpl>[0]) {
return await listProductsImpl(params);
}
export async function createProduct(data: Record<string, unknown>) {
return await createProductImpl(data);
}
export async function updateProduct(id: string, data: Record<string, unknown>) {
return await updateProductImpl(id, data);
}
export async function deleteProduct(id: string) {
return await deleteProductImpl(id);
}
export async function bulkDelete(ids: string[]) {
return await bulkDeleteProducts(ids);
}
export async function bulkCopy(ids: string[]) {
return await bulkCopyProducts(ids);
}
export async function bulkUpdate(ids: string[], updateData: unknown) {
return await bulkUpdateProducts(ids, updateData);
}3. Connecter les actions à la table
Dans votre config de table, retournez ces Server Actions depuis getTableActions :
// app/example/setup/table-config.ts
import {
listProducts,
createProduct,
updateProduct,
deleteProduct,
bulkDelete,
bulkCopy,
bulkUpdate,
} from "../actions/products";
export const getTableActions = (tableType: string) => {
if (tableType === "products") {
return {
list: listProducts,
create: createProduct,
update: updateProduct,
delete: deleteProduct,
bulkDelete: bulkDelete,
bulkCopy: bulkCopy,
bulkUpdate: bulkUpdate,
};
}
};bulkDelete, bulkCopy, bulkUpdate sans : est aussi un raccourci JavaScript valide quand la variable et la clé ont le même nom.
Quand la table a besoin de données ou exécute une action, elle appellera ces fonctions. Dans Next.js, elles s'exécutent côté serveur ; les paramètres et valeurs de retour sont sérialisés automatiquement.
4. Précharger la première page côté serveur
Pour les dashboards et les pages d'administration, privilégiez un Server Component pour la première lecture. La table client reçoit cette même première page via initialData, puis TanStack Query gère les refresh, retry, tri, filtres et pagination suivants avec la même Server Action list.
// app/example/products-page.tsx
import { listProducts } from "./actions/products";
import { ProductsTableClient } from "./products-table-client";
export default async function ProductsPage() {
const initial = await listProducts({ limit: 10, page: 1 });
return (
<ProductsTableClient
initialData={initial.data}
initialPageCount={initial.meta.pageCount}
initialRowCount={initial.meta.totalCount}
/>
);
}// app/example/products-table-client.tsx
"use client";
import { DataTable } from "@/components/ui/yayaw-table";
import { listProducts } from "./actions/products";
import { getTableConfig } from "./setup/table-config";
export function ProductsTableClient({
initialData,
initialPageCount,
initialRowCount,
}: {
initialData: Record<string, unknown>[];
initialPageCount: number;
initialRowCount: number;
}) {
return (
<DataTable
getTableActions={() => ({ list: listProducts })}
getTableConfig={getTableConfig}
initialData={initialData}
initialPageCount={initialPageCount}
initialRowCount={initialRowCount}
tableType="products"
/>
);
}Gardez l'autorisation, le scope tenant et le filtrage des données sensibles dans le code serveur qui appelle listProducts. Les props initial* sont seulement des lignes et compteurs sérialisés pour le premier rendu ; elles ne remplacent pas la Server Action utilisée par la table après hydratation.
Structure des paramètres list
L'action list reçoit un objet unique avec :
| Key | Type | Description |
|---|---|---|
page | number | Index de page en base 1. |
limit | number | Taille de page. |
filters | Record<string, unknown> | Filtres colonnes (clé = id colonne, valeur = valeur du filtre). |
advancedFilters | array | Règles de filtre avancé (columnId, operator, values, type, isActive). |
orderBy | Record<string, "asc" | "desc"> | Tri par champ ; seule la première clé est utilisée pour le tri mono-colonne. |
search | string | Terme de recherche global. |
Retour attendu :
{ data: T[]; meta?: { pageCount?: number; totalCount?: number } }Mode server-side (par défaut)
Yayaw Table exécute toujours filtrage, pagination et tri en mode server-side. Aucune option manual* n'est requise dans la config table.
Votre action list doit gérer search, filters, advancedFilters, orderBy, page et limit.
Application d'exemple
La route /example tourne actuellement en mode local pour que les modifications restent interactives sans backend : elle utilise app/[locale]/example/lib/products-local-actions.ts avec stockage dans le localStorage du navigateur.
Les fichiers de référence Server Action sont toujours présents dans app/[locale]/example/actions/products.ts et app/[locale]/example/lib/products-server.ts. Connectez ces actions via getTableActions si vous voulez la même page en mode serveur complet.
Voir aussi :