Capítulo 25 de 54

Chapter 25: Table Context Guide

Core Idea

Registered createTableHook components (table.PaginationControls, cell.TextCell, header.SortIndicator) read their instance from React context via useTableContext()/useCellContext()/useHeaderContext() rather than through props — but most cell/header components don't even need that: cell.table, header.table, row.table already link back, so context is really for components with no instance to start from (a standalone toolbar or pagination control).

Key Concepts

  • Providers: <table.AppTable> provides the table instance; <table.AppCell cell={cell}> provides the cell instance; <table.AppHeader header={h}> / <table.AppFooter header={h}> provide the header instance. Everything registered under tableComponents/cellComponents/headerComponents reads back via the matching hook.
  • You often don't need useTableContext() at all. cell.table, header.table, row.table are already on the instance you have — e.g. call cell.table.nextPage() directly from inside a cell component instead of reaching for context. Reserve useTableContext() for components genuinely disconnected from any instance.
  • Import the context hooks from your own createTableHook call site, not from the package directly — that's the only way they come back typed with your TFeatures and your registered component maps (so table.PaginationControls/cell.TextCell type-check).
  • createTableHookContexts for prop-drilling avoidance, independent of createTableHook: column/row/cell/header instances are stable references, so putting one on a context and reading it deep in a subtree is safe — the reference doesn't change on state updates, so context consumers won't spuriously re-render from state changes alone.
  • The trap: that same stability means a component reading a state-dependent method off a context-provided instance (header.column.getIsSorted(), cell.row.getIsSelected(), cell.getValue()) will not re-render when that state changes, because the instance reference itself didn't change. Wrap those specific reads in Subscribe/useSelector, exactly as in the React Compiler Guide's nested-component pattern — it's the same underlying issue.
  • table is the one exception — never put it on your own context as-is. useTable's React-facing return value gets a fresh reference whenever its selected state changes (that's intentional, so compiler-memoized JSX invalidates correctly) — so it is explicitly not stable, and providing it through your own createContext re-renders every consumer whenever the providing component re-renders. If you need a stable handle to pass down, provide table.store or a specific atom (table.atoms.rowSelection) instead, and read with Subscribe/useSelector, or keep the core table in a ref.
  • Default context is shared and module-scoped — correct for independent tables anywhere in the tree, including side-by-side or across routes, with zero setup.
  • Scoped contexts (createTableHookContexts) only matter when one table setup's provider is nested inside another's, where a consumer could otherwise read the wrong (nearest) provider. Contexts from createTableHookContexts are typed with TFeatures only (not your component maps) — prefer the hooks from your actual createTableHook call for full typing; use the createTableHookContexts hooks only from a module that can't import the createTableHook result.

Code Examples

// no context needed — the instance you already have links to its table
function TextCell() {
  const cell = useCellContext<string>()
  return <span>{cell.getValue()}</span>
}
function PaginationControls() {
  const table = useTableContext()          // genuinely disconnected — needs context
  return <button onClick={() => table.nextPage()}>Next</button>
}

// stable prop-drilling escape hatch, unrelated to createTableHook
const { cellContext, useCellContext } = createTableHookContexts<typeof features>()
  • What it demonstrates: useTableContext() earning its keep only for components with no instance already in hand; createTableHookContexts as a general stable-value context helper.

Key Takeaways

  1. Check for cell.table/row.table/header.table before reaching for useTableContext() — it's usually unnecessary.
  2. A state-dependent method read off a context-provided (or prop-drilled) instance needs Subscribe/useSelector around it, or it silently goes stale — this is the same trap as the React Compiler Guide's nested-component case, just via context instead of props.
  3. Never provide the raw useTable return value on your own context — provide table.store or a specific atom instead if you need a stable handle.

Connects To

  • React Compiler Guide: the identical staleness trap via props instead of context.
  • Composable Tables (createTableHook) Guide: where these providers/hooks come from.
  • Table State (React) Guide: table.store/table.atoms as the actually-stable alternative to the raw table object.