Errors and fixes

TanStack Table: flexRender and the most common errors

flexRender takes a render function and the cell context. Errors come from a missing half, a renderer that returns an object, or a column not in the list.

Key points

  • The correct form is `flexRender(cell.column.columnDef.cell, cell.getContext())`.
  • If the renderer returns an object, React rejects it: return a node or a string.
  • If the cell is blank, the column is not in the `columns` list, or the id does not match.

Short answer: flexRender takes two arguments, a renderer and the cell context. Errors come from a missing one of the two, from a renderer that returns an object, or from a column that is not in the list.

The mechanism: two arguments, no magic

flexRender is a three-line function. If the first argument is a function it calls it with the context; if it is already a React node it returns it; if it is a string or a number it lets it through. It does not check the column and knows nothing about the table.

The cell knows what to show because the column definition tells it. When cell is missing, the first argument arrives empty and the function returns null: React does not complain and the cell stays a mute rectangle. That is why the most frequent case produces no error, just a blank column.

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())}
        
      ))}
    
  ))}

Both keys are required: row.id and cell.id. Without them React warns about keys, and rendering can reuse the wrong cell after a reorder.

The errors, one by one

ErrorCauseFix
missing rendererthe column has no `cell`pass the value as a string, or define `cell`
Objects are not valid as a React childthe renderer returns an objectreturn a node or a string
empty table`getRowModel()` is missingcall the method after `useReactTable`
column absent`accessorKey` does not match the dataalign the field name

The second message looks the scariest and needs one line to explain: you returned row.original instead of a field. React accepts text and nodes, not objects. A string, a number or an element: anything else must be extracted first.

Diagnosis: print the column contract

Before reading the DOM, look at what the definition holds. A tabular log tells you in one shot which columns have a renderer, which have an accessor and which have neither.

console.table(
  columns.map((c) => ({
    id: c.id,
    accessor: "accessorKey" in c ? c.accessorKey : c.accessorFn ? "fn" : "display",
    cell: typeof c.cell,
  })),
);

When the log shows the string type for cell, no renderer exists. An accessor of "display" means the column reads no field at all: it is action-only, and should be treated that way. The third check is the field name: an accessorKey missing from the data yields an empty cell with no warning.

A cell with a component

const columns: ColumnDef[] = [
  { accessorKey: "id", header: "Ordine" },
  {
    accessorKey: "status",
    header: "Stato",
    cell: ({ row }) => ,
  },
];

The renderer is a function that receives the context: flexRender calls it and inserts the result. Write cell: <StatusBadge /> and you pass an element instead of a function, so for the table that column has no callable renderer.

When the usual fix makes it worse

The first reaction to constant re-rendering is to wrap everything in useMemo with empty dependencies. The columns freeze on the first render: the table stops bouncing, and it also stops updating labels that depend on props, language, permissions, currency. The symptom turns into “filters work, headers do not”, which is harder to trace.

The second reaction is to change the keys to silence the warning: key={index} or a random value. Unstable keys lose the state of every cell, an uncontrolled input empties on each sort, and React rebuilds the DOM instead of reusing it.

One limit to budget for: it is not a data grid

TanStack Table is headless: it computes rows and cells, it draws nothing. It brings no virtualisation, no aria-sort, no focus management. With a few thousand rows the bottleneck is the number of DOM nodes, not flexRender: you need a virtualiser or pagination, and the accessibility of the table stays your job.

Prevention

  • Define the columns as a constant, not inside the render.
  • Type the columns with ColumnDef<T>: field errors show up in the editor.
  • Pin the version: signatures and names change between v8 and the v9 alphas.
  • Add a test that asserts the text of the first cell, not just the row count.

Where to go next

In the catalogue: data table with preview, components and blocks. The guides: accessibility checklist and alternatives to MUI. The comparison: Radix, Base UI and React Aria.

Frequently asked questions

Why does flexRender return nothing?

Because the first argument is not a function: the column declares no cell, so there is no renderer to call and nothing gets drawn.

How do I render a component in a cell?

Declare cell: ({ row }) => <StatusBadge status={row.original.status} /> and let flexRender call it with the cell context.

Should I call getRowModel()?

Yes, it produces the visible rows. Without it the array stays empty and the table shows the header alone.

Why is rendering slow with many rows?

Because you render every row at the same time. You need virtualisation or pagination; rendering a single cell is not the bottleneck.

Do the APIs change between versions?

Yes. Names and signatures change between stable v8 and the v9 alphas, so pin the version in package.json and read the docs for that one.