Capítulo 34 de 54

Chapter 34: Column Visibility (React) Guide

Core Idea

columnVisibilityFeature is a simple { [columnId]: boolean } map (absent or true = shown, false = hidden) — the one easy-to-miss requirement is that every render/count API has a "visible" variant, and using the plain variant silently ignores hidden columns.

Key Concepts

  • State shape: columnVisibility: Record<string, boolean>. A column is hidden only if explicitly false; missing from the map or true means shown.
  • Ownership: external atom via atoms: { columnVisibility: atom } (v9-recommended, e.g. to persist user column preferences) or classic state.columnVisibility + onColumnVisibilityChange; or just initialState.columnVisibility if you never need it outside the table. Don't set both initialState.columnVisibility and state.columnVisibilitystate wins and initialState is ignored.
  • enableHiding: false on a column def locks it visible (a required "id"/"actions" column, say) — column.getCanHide() reports false for it.
  • Toggle API set: column.getCanHide(), column.getIsVisible(), column.toggleVisibility(), column.getToggleVisibilityHandler() (bind directly to a checkbox's onChange).
  • Labeling a visibility-toggle menu: don't render column.columnDef.header directly there — it's a render template (string/JSX/function needing full header context), and a hidden column may not have an active header context to render it with. Use a stable label instead: a columnId → label map, typed column.columnDef.meta?.label, or fall back to column.id.
  • The "visible" API requirement: table.getAllLeafColumns()/row.getAllCells() do not account for visibility — use table.getVisibleLeafColumns()/row.getVisibleCells() instead when rendering the actual table. Header Group APIs (table.getHeaderGroups()) already account for visibility automatically.

Code Examples

const features = tableFeatures({ columnVisibilityFeature })
const table = useTable({ features, columns, data, initialState: { columnVisibility: { age: false } } })

// visibility toggle menu
{table.getAllColumns().map((col) => (
  <label key={col.id}>
    <input type="checkbox" checked={col.getIsVisible()} disabled={!col.getCanHide()}
      onChange={col.getToggleVisibilityHandler()} />
    {columnLabels[col.id] ?? col.id}
  </label>
))}

// rendering: always the "visible" variant
{row.getVisibleCells().map((cell) => /* ... */ null)}
  • What it demonstrates: a toggle menu using a stable label map (not columnDef.header), and the visibility-aware render call.

Key Takeaways

  1. Always render/count with the getVisible* variant — getAllLeafColumns()/getAllCells() ignore visibility entirely, and this is the single most common bug with this feature.
  2. Don't render column.columnDef.header as a label for a visibility-toggle UI — use a dedicated label source instead.
  3. enableHiding: false is the right way to lock a structural column (checkboxes, row actions) visible.

Connects To

  • Column Definitions Guide: meta, a good home for a stable column label.
  • Columns Guide: the general "column objects aren't for rendering directly" caution this reinforces for the header-as-label case.