This article also exists in: English

Nello sviluppo di dashboard enterprise, la gestione delle tabelle dati è spesso una delle sfide più complesse. In un mio progetto di prova, i componenti GridContainer.tsx e Grid.tsx rappresentano un eccellente esempio di come sfruttare la potenza di React Table (v7) per creare un’interfaccia flessibile, potente e altamente personalizzabile.

L’Approccio “Headless”

La particolarità di react-table è il suo essere una libreria headless. Non fornisce componenti UI (niente tag <table> pre-confezionati), ma mette a disposizione degli hook che gestiscono tutta la logica di stato: ordinamento, paginazione, espansione e selezione.

Nel test ho cercato di usare un’architettura con pattern Container/Presenter:

  • GridContainer: Il “Cervello”. Decide quali colonne mostrare, come formattare i link e se attivare la paginazione.
  • Grid: Il “Muscolo”. Prende le istruzioni e genera i tag HTML, gestisce i CSS e si assicura che l’utente veda fisicamente le icone di ordinamento o le righe espanse.

Componente GridContainer

In GridContainer.tsx, utilizziamo l’hook principale useTable combinandolo con diversi plugin:

const gridConfig = useTable(
    gridData,
    useSortBy,    // Gestisce l'ordinamento delle colonne
    useExpanded,  // Gestisce l'espansione delle righe
    !!withPagination && usePagination // Gestisce la paginazione (opzionale)
);

Questa modularità permette di caricare solo il codice necessario, mantenendo il componente leggero.

Il “Trucco” del Pre-processing delle Colonne

Una delle feature più interessanti del nostro GridContainer è il modo in cui trasforma una definizione di dati “piatta” in una UI ricca. Prima di passare le colonne a react-table, il componente esegue un mapping dinamico:

tableData.columns = tableData.columns.map((el) => {
    // 1. Iniezione dinamica dell'espansione
    if (!!subRow && !!el.NeedCellExpand) {
        el.Cell = ({ row }) => (
            <span {...row.getToggleRowExpandedProps()}>
                {row.isExpanded ? '👇' : '👉'}
            </span>
        );
    }
    // 2. Rendering condizionale di link e badge
    if (!!el.NeedCellLink) {
        el.Cell = ({ row }) => (
            <ButtonLink link={...} textButton={row.original[el.NeedCellLink]} />
        );
    }
    return el;
});

Particolarità: Row Expansion & Custom Cells

  • Row Expansion: Grazie a useExpanded, possiamo rendere le righe interattive. Il componente Grid.tsx riceve una funzione renderSubElement che permette di mostrare dettagli aggiuntivi sotto la riga principale senza sporcare la struttura della tabella madre.
  • Celle Polimorfiche: Invece di limitarci a mostrare testo, il componente trasforma i dati in ButtonLink, Badge o icone in base a flag specifici come NeedMultipleLink o NeedCellLink.

Evoluzione: Verso TanStack Table v8

Il mondo React corre veloce e react-table si è evoluto in TanStack Table v8. Se dovessimo aggiornare il nostro componente oggi, ecco le principali differenze che affronteremmo:

  1. TypeScript First: Mentre la v7 usa i plugin come argomenti di un hook, la v8 è stata completamente riscritta in TS. La definizione delle colonne diventerebbe più strutturata tramite un ColumnHelper.
  2. State Management: Nella v8, lo stato è più esplicito. Non si passano più i plugin come hook, ma si abilitano le “features” direttamente nell’oggetto di configurazione di useReactTable.
  3. Modularità Estrema: La v8 è ancora più leggera e rimuove completamente il concetto di “plugin” a favore di una configurazione dichiarativa.

Esempio di Refactoring (v7 vs v8)

Oggi (v7):

useTable({ columns, data }, useSortBy, usePagination)

Domani (v8):

useReactTable({
  data,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getSortedRowModel: getSortedRowModel(), // Feature di ordinamento esplicita
  getPaginationRowModel: getPaginationRowModel(), // Feature di paginazione
})

Componente Grid

Il componente Grid.tsx agisce come il Presentation Layer (o strato di visualizzazione) del sistema. Mentre GridContainer si occupa della logica di business e della configurazione dei plugin, Grid ha il compito di trasformare i dati elaborati da react-table in vero codice HTML.

Ecco di cosa si occupa nello specifico:

1. Rendering della Struttura HTML <table>

È qui che avviene il “matrimonio” tra la logica headless e il DOM. Il componente applica i cosiddetti prop getters forniti da react-table per garantire che la tabella sia accessibile e correttamente strutturata:

<table {...getTableProps()} className={css['grid--bordered']}>
    <thead>{/* ... */}</thead>
    <tbody {...getTableBodyProps()}>{/* ... */}</tbody>
</table>

2. Gestione dell’Interfaccia di Ordinamento

Grid.tsx si occupa di mostrare visivamente lo stato dell’ordinamento nelle testate delle colonne, aggiungendo indicatori visivi (frecce) in base allo stato isSorted e isSortedDesc:

<span>
    {column.isSorted
        ? column.isSortedDesc
            ? ' 🔽'
            : ' 🔼'
        : ''}
</span>

3. Ciclo di Vita delle Righe (prepareRow)

Un compito critico è l’esecuzione di prepareRow(row). In react-table, le righe vengono caricate pigramente (lazy); Grid prepara ogni riga appena prima del rendering, calcolando gli stili e le proprietà necessarie per le celle.

4. Rendering delle Sotto-Righe (Expanded Rows)

Il componente implementa la logica di visualizzazione per i dettagli aggiuntivi. Se una riga è espandibile e lo stato isExpanded è attivo, Grid.tsx inietta una riga extra nel <tbody> per renderizzare il renderSubElement:

{row.isExpanded ? (
    <tr>
        <td colSpan={visibleColumns.length}>
            {renderSubElement({ row, ...otherInfo })}
        </td>
    </tr>
) : null}

5. Debugging e Trasparenza

Include una modalità di debug che permette di visualizzare lo stato JSON interno dell’espansione, molto utile in fase di sviluppo per capire come sta reagendo il motore di react-table alle interazioni dell’utente.

Analisi visuale

Ecco una descrizione del flusso dei dati dalla sorgente fino al rendering finale nel DOM, dove si evidenzia il ruolo centrale della dipendenza react-table di TanStack.

Analisi Visuale

1. Il Trasformatore (GridContainer) Il GridContainer.tsx riceve i dati grezzi. Prima ancora che la libreria entri in gioco, esegue un “arricchimento” delle colonne. Se una colonna ha il flag NeedCellLink, viene iniettata una funzione Cell che renderizza un componente React (ButtonLink). Questo è un pattern potente: i dati decidono il loro aspetto.

2. Il Motore (react-table) In GridElementContainer, richiamiamo gli hook di react-table. Qui avviene la magia: la libreria prende le nostre colonne arricchite e i dati, restituendoci un oggetto gridConfig che contiene:

  • Lo stato (chi è espanso? quale pagina stiamo guardando?).
  • I “Prop Getters” (funzioni che generano gli attributi HTML corretti).

3. Gli Esecutori (Grid & PaginatedGrid) Questi componenti sono “stupidi” dal punto di vista della logica, ma “esperti” di layout. Ricevono tutto il necessario da gridConfig e si occupano di:

  • Iterare sulle righe e le celle.
  • Chiamare prepareRow (necessario per react-table v7).
  • Gestire il rendering condizionale delle sotto-righe (renderSubElement).

Perché questa separazione?

  • Testabilità: Puoi testare la logica di mapping in GridContainer separatamente dal layout.
  • Manutenibilità: Se decidi di cambiare il design delle tabelle (es. passare da una tabella bordata a una “striped”), devi modificare solo Grid.tsx, senza toccare la logica dei dati.
  • Performance: Usando useMemo in GridElementContainer, evitiamo ricalcoli pesanti di react-table ad ogni render del genitore.

Conclusione

L’implementazione di test con Grid.tsx e GridContainer.tsx con un’architettura Container/Presenter dimostra come un approccio basato sulla composizione di plugin possa risolvere scenari complessi di visualizzazione dati. Il passaggio alla v8 porterebbe una maggiore robustezza grazie al sistema di tipi di TypeScript, pur mantenendo intatta la filosofia “headless” che rende questo componente così versatile.


Ottimizzazioni consigliate

  • Migrazione a @tanstack/react-table: Per ottenere migliori performance e una developer experience superiore grazie ai tipi statici.
  • Virtualizzazione: Per tabelle con migliaia di righe, l’integrazione con react-virtual (sempre di TanStack) sarebbe il passo logico successivo.
  • Server-side Logic: Spostare l’ordinamento e la paginazione lato server per migliorare i tempi di caricamento iniziale su dataset massivi.

About the author

For the last 20 years, Stefano Frasca has worked with a variety of web technologies both backend and frontend. He is currently focused on front-end development. On his day to day job, he is working as a FSBO Area Leader at Immobiliare Labs IT. He has worked remotely for years, passionate about photography, food and code 😎
Do you want to know more? Visit my website!