This example demonstrates the createTableHook composition pattern for TanStack Table v9, similar to TanStack Form's createFormHook.
The hooks/table.ts file sets up everything in one place:
- Features -
_featuresdefines which table features are enabled - Row Models - Pre-configured sorted, filtered, and paginated row models
- Default Options - Any table options can be set as defaults (except columns/data/store/state/initialState)
- Contexts - Created internally, with
TFeaturesalready baked in - Context Hooks -
useTableContext,useCellContext,useHeaderContext- all typed! - Pre-bound Components - Table, cell, and header components
- Column Helper -
createAppColumnHelperpre-bound to your features
Because TFeatures is baked into the context hooks at creation time, your custom components don't need type annotations:
// components/table-components.tsx
function PaginationControls() {
const table = useTableContext() // TFeatures already known!
return <table.Subscribe selector={(s) => s.pagination}>...</table.Subscribe>
}Since TFeatures is configured once in createTableHook, the createAppColumnHelper only needs TData:
// TFeatures already bound - only need TData!
const columnHelper = createAppColumnHelper<Person>()
const columns = columnHelper.columns([
columnHelper.accessor('firstName', { header: 'First Name' }),
columnHelper.accessor('age', { header: 'Age' }),
])Since _features, _rowModels, and default options are configured once in createTableHook, useAppTable only needs columns and data:
const table = useAppTable({
columns,
data, // TData inferred from this
})Table Components (accessible via table.ComponentName):
PaginationControls- Full pagination UIRowCount- Row count displayTableToolbar- Header with title and actions
Cell Components (accessible via cell.ComponentName in AppCell):
TextCell- Generic text rendererNumberCell- Formatted number rendererStatusCell- Status badgeProgressCell- Progress barRowActionsCell- Row action buttons (view, edit, delete)
Header Components (accessible via header.ComponentName in AppHeader):
SortIndicator- Sort direction iconColumnFilter- Filter input
Footer Components (accessible via footer.ComponentName in AppFooter):
FooterColumnId- Display the column IDFooterSum- Sum aggregation for numeric columns
The useAppTable hook returns these wrapper components:
AppTable- Root wrapper providing table contextAppCell- Cell wrapper with pre-bound cellComponentsAppHeader- Header wrapper with pre-bound headerComponentsAppFooter- Footer wrapper with pre-bound headerComponents
All App wrapper components support an optional selector prop for optimized re-renders:
// Without selector - children is a function receiving the entity
<table.AppCell cell={c}>
{(cell) => <td><cell.TextCell /></td>}
</table.AppCell>
// With selector - children receives both entity and selected state
<table.AppCell cell={c} selector={(state) => state.columnFilters}>
{(cell, filters) => <td>{filters.length} filters</td>}
</table.AppCell>
// AppTable with selector
<table.AppTable selector={(state) => state.pagination}>
{(pagination) => <div>Page {pagination.pageIndex + 1}</div>}
</table.AppTable>This wraps the children in a Subscribe component for fine-grained reactivity.
src/
├── hooks/
│ └── table.ts # createTableHook setup with features, row models, and components
├── components/
│ ├── cell-components.tsx # TextCell, NumberCell, StatusCell, ProgressCell, RowActionsCell
│ ├── header-components.tsx # SortIndicator, ColumnFilter
│ └── table-components.tsx # PaginationControls, RowCount, TableToolbar
├── main.tsx # App entry point with table component
├── makeData.ts # Mock data generator
└── index.css # Styles
cd examples/react/large-table
pnpm install
pnpm dev