Capítulo 41 de 54

Chapter 41: Expanding (React) Guide

Core Idea

rowExpandingFeature covers two distinct use cases that share the same state/toggle mechanism: real hierarchical sub-rows (via getSubRows) and custom "detail panel" UI unrelated to the table's own columns (via getRowCanExpand overriding the default sub-rows check) — picking the wrong one is the main source of confusion.

Key Concepts

  • Setup: rowExpandingFeature + expandedRowModel: createExpandedRowModel() for client-side; manualExpanding: true for server-side (you own the expansion in your own data model, row model skipped).
  • Sub-rows use case: if a row object already nests its children (e.g. { ..., children: Person[] }), point getSubRows: (row) => row.children at it — the table then understands where to find children, and row.getCanExpand() returns true automatically wherever subRows are found. Beware: a complex getSubRows runs for every row and sub-row, so keep it cheap; async functions aren't supported.
  • Detail-panel use case: when expanded content isn't structurally sub-rows at all (an unrelated detail panel), override getRowCanExpand: (row) => true (or your own predicate) instead of relying on subRows detection, then render a second <tr> conditionally on row.getIsExpanded() with a single <td colSpan={row.getAllCells().length}> holding custom content.
  • State shape: ExpandedState = true | Record<string, boolean>true means every row expanded; a record expands only the ids present with value true (a false entry, or a missing id, means collapsed).
  • Ownership: external atom via atoms: { expanded: atom } (v9-recommended) or classic state.expanded + onExpandedChange.
  • No built-in toggle UI — wire your own, typically a button in a column's cell renderer: row.getCanExpand() && <button onClick={row.getToggleExpandedHandler()}>{row.getIsExpanded() ? '▼' : '▶'}</button>.
  • Row APIs: row.getCanExpand(), row.getIsExpanded(), row.getIsAllParentsExpanded() (useful to skip rendering a deeply nested row whose ancestor chain isn't fully open), row.toggleExpanded(), row.getToggleExpandedHandler().
  • Table APIs: table.getCanSomeRowsExpand(), getIsAllRowsExpanded(), getIsSomeRowsExpanded(), getExpandedDepth(), toggleAllRowsExpanded()/getToggleAllRowsExpandedHandler(), table.setExpanded(...), resetExpanded() (→ initialState.expanded) / resetExpanded(true) (→ fully collapsed).
  • Interaction with other row-model stages: filtering (filterFromLeafRows/maxLeafRowFilterDepth, same options as the Column Filtering Guide — needed here because expansion introduces the parent/child relationship that leaf-up filtering walks); pagination (paginateExpandedRows: false keeps a parent's children always on the parent's page, at the cost of rendering more rows than the nominal page size — default is true, so expanded rows paginate along with everything else and a subtree can span pages); row pinning (works the same as regular row pinning); sorting (expanded rows sort along with the rest by default).
  • Auto-reset: when grouping is also registered, expanded state auto-resets whenever the grouped row model recomputes (e.g. on data or grouping-state change) — disabled automatically once manualExpanding: true, or override explicitly with autoResetExpanded: false (or the global autoResetAll: false). Set autoResetExpanded: false deliberately whenever inline data editing would otherwise collapse the user's open rows on every keystroke — pair it with autoResetPageIndex: false if pagination is also in play.

Code Examples

// hierarchical sub-rows
const table = useTable({ features, columns, data, getSubRows: (row) => row.children })

// detail-panel UI, unrelated to real sub-rows
const table = useTable({ features, columns, data, getRowCanExpand: () => true })
<>
  <tr>{row.getVisibleCells().map((cell) => <td key={cell.id}><table.FlexRender cell={cell} /></td>)}</tr>
  {row.getIsExpanded() && <tr><td colSpan={row.getAllCells().length}>{/* custom detail UI */}</td></tr>}
</>
  • What it demonstrates: the two setups side by side — getSubRows for real hierarchy vs. getRowCanExpand + a manually-rendered second <tr> for unrelated detail content.

Key Takeaways

  1. Decide up front which use case applies — real sub-rows (getSubRows) vs. detail panel (getRowCanExpand override) — they use different setup even though the toggle APIs are identical.
  2. autoResetExpanded: false is essential whenever the table supports inline editing; otherwise every edit silently collapses open rows.
  3. paginateExpandedRows: false is the fix when users expect a parent's children to always render together with it, at the cost of variable page sizes.

Connects To

  • Rows Guide: subRows, depth, parentId — the underlying row-tree properties this feature toggles visibility of.
  • Column Filtering Guide: filterFromLeafRows/maxLeafRowFilterDepth, needed once filtering and expansion combine.
  • Grouping (React) Guide: how the auto-reset-on-grouping-recompute interaction works.