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

ErroreCausaCorrezione
renderer undefinedla colonna non ha `cell`passa il valore come stringa o definisci `cell`
Objects are not valid as a React childil renderer restituisce un oggettorestituisci un nodo o una stringa
tabella vuotamanca `getRowModel()`chiama il metodo dopo `useReactTable`
colonna assente`accessorKey` diverso dai datiallinea 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.