Errori e fix
TanStack Table: flexRender e gli errori più comuni
flexRender accetta una funzione di render e il contesto della cella. Gli errori nascono quando una delle due parti manca, quando il renderer restituisce un oggetto o quando la colonna non esiste.
In sintesi
- La forma corretta è `flexRender(cell.column.columnDef.cell, cell.getContext())`.
- Se il renderer restituisce un oggetto, React lo rifiuta: restituisci un nodo o una stringa.
- Se la cella è vuota, la colonna non è nella lista `columns` o l'id non corrisponde.
Risposta breve: flexRender riceve due argomenti, un renderer e il contesto della cella. Gli errori nascono quando manca uno dei due, quando il renderer restituisce un oggetto, o quando la colonna non è nella lista.
Il meccanismo: due argomenti, nessuna magia
flexRender è una funzione di tre righe. Se il primo argomento è una funzione la chiama con il contesto; se è già un nodo React lo restituisce; se è una stringa o un numero lo lascia passare. Non controlla la colonna e non conosce la tabella.
La cella sa cosa mostrare perché la definizione di colonna glielo dice. Quando manca cell, il primo argomento arriva vuoto e la funzione restituisce null: React non protesta, la cella resta un rettangolo muto. Ecco perché il caso più frequente non produce un errore ma una colonna bianca.
import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
});
{table.getRowModel().rows.map((row) => (
{row.getVisibleCells().map((cell) => (
{flexRender(cell.column.columnDef.cell, cell.getContext())}
))}
))}
Le due chiavi sono obbligatorie: row.id e cell.id. Senza, React avvisa sulle key e il rendering può riusare la cella sbagliata dopo un riordino.
Gli errori, uno per uno
| Errore | Causa | Correzione |
|---|---|---|
| renderer undefined | la colonna non ha `cell` | passa il valore come stringa o definisci `cell` |
| Objects are not valid as a React child | il renderer restituisce un oggetto | restituisci un nodo o una stringa |
| tabella vuota | manca `getRowModel()` | chiama il metodo dopo `useReactTable` |
| colonna assente | `accessorKey` diverso dai dati | allinea il nome del campo |
Il secondo messaggio è quello che spaventa di più e si spiega in una riga: hai restituito row.original invece di un campo. React accetta testo e nodi, non oggetti. Stringa, numero o elemento: tutto il resto va estratto prima.
Diagnosi: stampa il contratto delle colonne
Prima di leggere il DOM, guarda cosa contiene la definizione. Un log tabellare dice in un colpo quali colonne hanno un renderer, quali un accessor e quali nessuno dei due.
console.table(
columns.map((c) => ({
id: c.id,
accessor: "accessorKey" in c ? c.accessorKey : c.accessorFn ? "fn" : "display",
cell: typeof c.cell,
})),
);
Se cell vale "undefined", il renderer non esiste. Se accessor vale "display", la colonna non legge nessun campo: è una colonna di sola azione, e va trattata come tale. Il terzo controllo è il nome del campo: un accessorKey che non esiste nei dati produce una cella vuota senza avvisi.
Una cella con un componente
const columns: ColumnDef[] = [
{ accessorKey: "id", header: "Ordine" },
{
accessorKey: "status",
header: "Stato",
cell: ({ row }) => ,
},
];
Il renderer è una funzione che riceve il contesto: flexRender la chiama e inserisce il risultato. Se scrivi cell: <StatusBadge />, passi un elemento invece di una funzione, e per la tabella quella colonna non ha un renderer chiamabile.
Quando la correzione comune peggiora le cose
La prima reazione al re-render continuo è avvolgere tutto in useMemo con dipendenze vuote. Le colonne si congelano alla prima render: la tabella smette di rimbalzare e smette anche di aggiornare le etichette che dipendono da props, lingua, permessi, valuta. Il sintomo diventa «i filtri funzionano, le intestazioni no», che è più difficile da attribuire.
La seconda è cambiare le key per zittire l'avviso: key={index} o un valore casuale. Con le key instabili perdi lo stato di ogni cella, un input non controllato si svuota a ogni ordinamento, e React ricostruisce il DOM invece di riusarlo.
Un limite da mettere in conto: non è un data grid
TanStack Table è headless: calcola righe e celle, non disegna niente. Non porta virtualizzazione, non porta aria-sort, non porta gestione del focus. Con qualche migliaio di righe il collo di bottiglia è il numero di nodi nel DOM, non flexRender: servono un virtualizzatore o la paginazione, e l'accessibilità della tabella resta lavoro tuo.
Prevenzione
- Definisci le colonne come costante, non dentro il render.
- Tipizza le colonne con
ColumnDef<T>: gli errori di campo emergono in editor. - Fissa la versione: tra v8 e le alpha della v9 cambiano firme e nomi.
- Aggiungi un test che verifichi il testo della prima cella, non solo il numero di righe.
Dove continuare
Nel catalogo: data table con preview, componenti e blocchi. Le guide: checklist accessibilità e alternative a MUI; il confronto: Radix, Base UI e React Aria.
Domande frequenti
Perché `flexRender` restituisce undefined?
Perché il primo argomento non è una funzione: la colonna non dichiara `cell`, quindi non c'è nessun renderer da chiamare e non viene disegnato nulla.
Come renderizzo un componente in una cella?
Definisci `cell: ({ row }) => <StatusBadge status={row.original.status} />` e lascia che `flexRender` chiami la funzione con il contesto della cella.
Devo chiamare `getRowModel()`?
Sì, è il metodo che produce le righe visibili. Senza, l'array è vuoto e la tabella mostra soltanto l'intestazione.
Perché il rendering è lento con molte righe?
Perché stai renderizzando tutte le righe contemporaneamente. Serve virtualizzazione o paginazione: il rendering delle singole celle non è il collo di bottiglia.
Le API cambiano tra le versioni?
Sì. Tra la v8 stabile e le alpha della v9 cambiano nomi e firme: fissa la versione nel package.json e leggi la documentazione di quella.