TL;DR : @snowpact/react-tanstack-query-table est un wrapper léger autour de TanStack
Table + TanStack Query qui élimine le boilerplate tout en vous laissant le contrôle total sur l’apparence.
Le problème des librairies « headless »
Si vous avez déjà utilisé TanStack Table (anciennement React Table), vous connaissez la situation :
une librairie puissante, flexible, avec zéro opinion sur le rendu. C’est génial en théorie.
En pratique ? Vous passez des heures à :
- Écrire le même code de pagination sur chaque projet
- Recréer les composants de tri, recherche, filtres
- Gérer manuellement le state avec TanStack Query
- Adapter les styles à votre design system (Shadcn, MUI, etc.)
Le résultat : un énorme boilerplate que vous copiez-collez de projet en projet. Et à chaque nouveau projet, vous
redécouvrez les mêmes edge cases.
Notre approche : Headless + Registry
Chez Snowpact, on a pris une approche différente. Au lieu de créer une librairie opinionated qui impose ses styles,
on a créé un système de registry :
- Le package fournit la logique (pagination, tri, recherche, actions)
- Vous injectez vos composants UI via une config unique
- Vous personnalisez les styles Tailwind comme vous voulez
Résultat : 0 dépendance sur un design system, mais 0 boilerplate aussi.
Installation
npm install @snowpact/react-tanstack-query-table
Peer Dependencies
Le package s’intègre avec votre stack existante :
| Dépendance | Version | Usage |
|---|---|---|
react |
>= 18.0 | Framework UI |
react-dom |
>= 18.0 | DOM rendering |
@tanstack/react-table |
>= 8.0 | Logique de table (headless) |
@tanstack/react-query |
>= 5.0 | Gestion du state serveur |
Quick Setup (5 minutes)
1. Configurez le registry une seule fois
// configs/setupSnowTable.tsx
import { setupSnowTable } from '@snowpact/react-tanstack-query-table';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { useConfirm } from '@/components/ConfirmDialog';
export function setupSnowTableConfig() {
setupSnowTable({
// Traductions
useTranslation: () => {
const { t } = useTranslation();
return { t };
},
// Links (react-router, Next.js, etc.)
LinkComponent: Link,
// Dialog de confirmation (optionnel)
useConfirm: () => {
const { confirm } = useConfirm();
return { confirm };
},
// Personnalisation des styles Tailwind
styles: {
state: {
active: 'ring-2 ring-primary/40',
focus: 'ring-2 ring-primary/40',
},
table: {
rowActive: 'bg-primary/10',
},
},
});
}
2. Appelez le setup au démarrage
// main.tsx
import { setupSnowTableConfig } from './configs/setupSnowTable';
setupSnowTableConfig();
ReactDOM.createRoot(document.getElementById('root')!).render(
<App />
);
3. Créez votre première table
// components/UserTable.tsx
import { SnowClientDataTable, SnowColumnConfig } from '@snowpact/react-tanstack-query-table';
import { Edit, Trash2 } from 'lucide-react';
type User = {
id: string;
name: string;
email: string;
createdAt: string;
};
const columns: SnowColumnConfig<User>[] = [
{ key: 'name', sortable: true },
{ key: 'email', sortable: true },
{
key: 'createdAt',
label: 'Inscrit le',
render: (user) => new Date(user.createdAt).toLocaleDateString(),
},
];
export const UserTable = () => {
return (
<SnowClientDataTable
queryKey={['users']}
fetchAllItemsEndpoint={async () => {
const res = await fetch('/api/users');
return res.json();
}}
columnConfig={columns}
actions={[
{
type: 'click',
icon: Edit,
label: 'Modifier',
onClick: (user) => console.log('Edit', user),
},
{
type: 'endpoint',
icon: Trash2,
label: 'Supprimer',
variant: 'danger',
endpoint: (user) => fetch(`/api/users/${user.id}`, { method: 'DELETE' }),
confirm: {
title: 'Confirmer la suppression',
content: 'Cette action est irréversible.',
},
},
]}
enableGlobalSearch
enablePagination
enableSorting
/>
);
};
C’est tout. Pas de useState, pas de useEffect, pas de gestion manuelle du cache.
Client vs Server : Deux Modes
SnowClientDataTable
Charge toutes les données en une fois, puis filtre/trie côté client.
<SnowClientDataTable
queryKey={['products']}
fetchAllItemsEndpoint={fetchAllProducts}
columnConfig={columns}
enableGlobalSearch // Recherche fuzzy côté client
enablePagination // Pagination côté client
/>
Idéal pour : Petits datasets (< 1000 items), admin panels, données qui changent peu.
SnowServerDataTable
Délègue pagination, tri et recherche au backend.
<SnowServerDataTable
queryKey={['orders']}
fetchServerEndpoint={async (params) => {
// params: { limit, offset, search, sortBy, sortOrder, filters }
const res = await api.getOrders(params);
return {
items: res.data.orders,
totalItemCount: res.data.total,
};
}}
columnConfig={columns}
enableGlobalSearch
enablePagination
/>
Idéal pour : Gros datasets, données sensibles, pagination SQL.
Fonctionnalités Avancées
Prefilters (Onglets de filtre)
<SnowServerDataTable
prefilters={[
{ id: 'active', label: 'Actifs' },
{ id: 'archived', label: 'Archivés' },
{ id: 'all', label: 'Tous' },
]}
fetchServerEndpoint={async (params) => {
// params.prefilter = 'active' | 'archived' | 'all'
return api.getUsers({ status: params.prefilter });
}}
/>
Filtres Dynamiques
<SnowServerDataTable
filters={[
{
key: 'status',
label: 'Statut',
options: [
{ label: 'En attente', value: 'pending' },
{ label: 'Validé', value: 'validated' },
],
multipleSelection: true,
},
{
key: 'category',
label: 'Catégorie',
options: categories.map(c => ({ label: c.name, value: c.id })),
},
]}
/>
Configuration des Colonnes
<SnowClientDataTable
enableColumnConfiguration // Affiche le bouton :gear:
columnConfig={[
{ key: 'id', meta: { defaultHidden: true } }, // Cachée par défaut
{ key: 'email', meta: { width: '200px' } },
]}
/>
Persistence du State
<SnowServerDataTable
persistState // Sauvegarde filtres/tri dans l'URL
// L'utilisateur peut partager l'URL avec ses filtres
/>
Tailwind CSS v4 : Configuration
Si vous utilisez Tailwind v4, ajoutez la directive @source pour scanner les classes du package :
/* main.css */
@import 'tailwindcss';
/* Scanne les classes du package npm */
@source "../node_modules/@snowpact/react-tanstack-query-table/dist/index.js";
Tableau des Fonctionnalités
| Fonctionnalité | Client | Server | Description |
|---|---|---|---|
| Pagination | ✅ | ✅ | Côté client ou serveur |
| Tri | ✅ | ✅ | Multi-colonnes supporté |
| Recherche globale | ✅ Fuzzy | ✅ Backend | Recherche full-text |
| Prefilters | ✅ | ✅ | Onglets de filtre rapides |
| Filtres dynamiques | ✅ | ✅ | Dropdowns multi-sélection |
| Actions inline | ✅ | ✅ | Click, Link, Endpoint |
| Actions dropdown | ✅ | ✅ | Menu « … » pour économiser l’espace |
| Confirmation dialog | ✅ | ✅ | Via useConfirm injectable |
| Config colonnes | ✅ | ✅ | Afficher/masquer colonnes |
| Persistence URL | ✅ | ✅ | Filtres dans l’URL |
| Active row | ✅ | ✅ | Highlight de ligne |
| Custom render | ✅ | ✅ | JSX personnalisé par cellule |
| Empty state | ✅ | ✅ | Message personnalisable |
| Loading state | ✅ | ✅ | Skeleton intégré |
| i18n | ✅ | ✅ | Via hook injectable |
Pourquoi pas X ?
| Alternative | Problème |
|---|---|
| AG Grid | Payant, lourd, style imposé |
| MUI DataGrid | Dépendance MUI, style imposé |
| Mantine DataTable | Dépendance Mantine |
| TanStack Table seul | Trop de boilerplate |
| Shadcn DataTable | Copier-coller, pas de package |
SnowTable : Léger (< 50KB), aucune dépendance UI imposée, un seul setup.
Liens
Article rédigé par l’équipe Snowpact. Retrouvez nos projets open-source sur GitHub.
