Capítulo 24 de 54
createTableHook({ features, ...defaults }) builds an app-specific useAppTable + createAppColumnHelper pair so every table in an app shares one feature set, row models, and default options without re-declaring tableFeatures() per table — and optionally registers reusable cell/header/table components too.
features/shared defaults (debugTable, enableSortingRemoval, etc.) once via createTableHook; each table's call site (useAppTable({ key, columns, data, ...overrides }, selector)) only supplies what's unique to it. Per-call options override the hook's defaults for that one table.createAppColumnHelper<TData>() is already bound to the app's features, so individual tables don't thread typeof features through every column definition — a real ergonomic win once you have more than one or two tables.table.getHeaderGroups(), table.FlexRender) exactly like a standalone useTable table — you don't need the component-registry machinery to benefit from shared features/options.createTableHook can also accept tableComponents/cellComponents/headerComponents maps. Column definitions then reference them directly inside cell/header (e.g. cell: ({ cell }) => <cell.TextCell />), and registered components read the current instance via useTableContext()/useCellContext()/useHeaderContext() (React context, not props) instead of needing that instance threaded down manually.table.AppTable, table.AppCell, table.AppHeader, table.AppFooter are the wrappers that provide those context values while rendering — <table.AppTable selector={...}> also doubles as the state-subscription boundary for that render, same pattern as table.Subscribe.createTableHook — that's what makes table.PaginationControls, cell.TextCell, header.SortIndicator fully typed (bound to your TFeatures and your registered component maps).Person and Product) can share one createTableHook call — each gets its own createAppColumnHelper<TData>() bound to the same shared features/components, while each table still owns its own data and columns.useAppTable tables share one module-scoped context, which is fine for independent tables anywhere in the tree. Only reach for createTableHookContexts (a separate isolated context set passed into createTableHook) when you nest one table setup's provider inside another's and a consumer would otherwise read the wrong (nearest) provider.// hooks/table.ts — shared setup, once
const features = tableFeatures({ rowSortingFeature, sortedRowModel: createSortedRowModel(), sortFns })
export const { useAppTable, createAppColumnHelper } = createTableHook({ features, debugTable: true })
// PersonTable.tsx — per-table
const columnHelper = createAppColumnHelper<Person>()
const columns = columnHelper.columns([columnHelper.accessor('firstName', { cell: (i) => i.getValue() })])
function PersonTable({ data }: { data: Person[] }) {
const table = useAppTable({ key: 'people', columns, data }, (s) => ({ sorting: s.sorting }))
return <table>{/* table.getHeaderGroups() / table.getRowModel(), same as a plain useTable table */}</table>
}
createAppColumnHelper and useAppTable behave exactly like createColumnHelper/useTable, just pre-bound to shared config — no new mental model, just less repetition.createTableHook as soon as an app has more than one table sharing the same features/defaults — it removes the repeated tableFeatures() boilerplate and keeps every table's typeof features consistent.createTableHookContexts when table setups are genuinely nested.table/cell/header from context, including the "why table itself is not a stable context value" gotcha.tableFeatures()/createTableHook pattern.createColumnHelper, the non-app-hook version createAppColumnHelper builds on.