Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits.
Read @tanstack/table-core#migrate-v8-to-v9, getting-started, and table-state. Use this as the exhaustive Preact migration checklist. Verify the exact v9 APIs in the installed declarations before assuming a different v9 version has the same surface.
Framework prerequisite: Preact 10 or newer (preact >=10).
Target architecture
import {
createSortedRowModel,
rowSortingFeature,
tableFeatures,
useTable,
} from '@tanstack/preact-table'
const features = tableFeatures({
rowSortingFeature,
sortedRowModel: createSortedRowModel(),
})
const table = useTable({ features, columns, data })
V8 did not have a first-party Preact adapter; many Preact apps used @tanstack/react-table through preact/compat. V9 uses native @tanstack/preact-table. Remove compatibility aliases that existed only for Table after replacing the imports. Prefer explicit features as the end state; stockFeatures is only a kitchen-sink migration shortcut.
Complete breaking-change map
Adapter, construction, and features
v8
v9
@tanstack/react-table through preact/compat
Native @tanstack/preact-table
useReactTable(options)
useTable({ ...options, features })
Every feature bundled
Register used *Feature objects with tableFeatures()
sortFns, filterFns, aggregationFns slots in tableFeatures()
Removed rowModels: { ... } object
Direct named slots in tableFeatures()
In the registry slots, register individually imported built-ins (filterFn_includesString, sortFn_alphanumeric, aggregationFn_sum, and so on) under their conventional keys alongside custom functions; the full filterFns/sortFns/aggregationFns registry objects still work but bundle every built-in.
Register each prerequisite feature before its row-model slot. Stock features are cellSelectionFeature, columnFilteringFeature, globalFilteringFeature, rowSortingFeature, rowPaginationFeature, rowSelectionFeature, rowExpandingFeature, rowPinningFeature, columnPinningFeature, columnVisibilityFeature, columnOrderingFeature, columnSizingFeature, columnResizingFeature, rowAggregationFeature, columnGroupingFeature, and columnFacetingFeature. Aggregation is independent from grouping: register rowAggregationFeature for aggregation APIs and add columnGroupingFeature only for grouped rows.
Preact state and subscriptions
v8
v9
table.getState()
Reactive table.state, snapshot table.store.state, or table.atoms.<slice>.get()
Top-level onStateChange
Per-slice callbacks or table.store.subscribe()
Whole-component state reads
Custom second-argument selector, table.Subscribe, or atom source
The default useTable selector subscribes the component to all registered state. Pass a selector for table.state, or () => null and subscribe lower:
Controlled state plus per-slice on[State]Change remains supported. For app-owned atoms, use useCreateAtom/useSelector from @tanstack/preact-store and pass them through options.atoms. An external atom wins over state for the same slice; do not mirror both ownership models.
Rendering and composition
Replace React-adapter flexRender(def, context) with <table.FlexRender cell={cell} /> or standalone <FlexRender ... />; the function remains for advanced cases.
Use tableOptions() for typed reusable option fragments.
Use createTableHook({ features, ...defaults }) for repeated app table conventions; it returns native app helpers such as useAppTable and createAppColumnHelper.
Call row/cell/column/header methods on their instance. Prototype methods lose this when destructured and do not appear in object spread, Object.keys, or JSON. Table-instance methods are not affected.
TypeScript and helpers
v8
v9
createColumnHelper<Person>()
createColumnHelper<typeof features, Person>()
Plain arrays that widen nested values
columnHelper.columns([...])
ColumnDef<TData>
ColumnDef<TFeatures, TData, TValue>
Column<TData>, Row<TData>, Table<TData>
Add TFeatures first
Cell<TData, TValue>
Cell<TFeatures, TData, TValue>
React module augmentation
Target @tanstack/preact-table
TableMeta<TData> / ColumnMeta<TData, TValue>
Add TFeatures, or use per-table tableMeta / columnMeta slots with metaHelper()
Global FilterFns, SortFns, AggregationFns, and FilterMeta augmentation
filterFns, sortFns, aggregationFns, and filterMeta feature slots
RowData = unknown
Record or array row data
Infer with typeof features; use StockFeatures only for deliberate stockFeatures typing.
Logical pinning does not style DOM direction automatically; use logical CSS insets. columnResizeDirection is unchanged.
v8
v9
Table enablePinning
enableColumnPinning plus enableRowPinning; column-level option remains
Combined sizing/resizing
columnSizingFeature plus columnResizingFeature for drag resizing
columnSizingInfo / onColumnSizingInfoChange
columnResizing / onColumnResizingChange
setColumnSizingInfo()
setColumnResizing()
sortingFn / sortingFns
sortFn / sortFns
getSortingFn() / getAutoSortingFn()
getSortFn() / getAutoSortFn()
SortingFn / SortingFns
SortFn / SortFns
row._getAllCellsByColumnId()
row.getAllCellsByColumnId()
table._getPinnedRows()
getTopRows(), getCenterRows(), or getBottomRows()
table._getFacetedRowModel()
Public faceting APIs on the relevant column/table
table._getFacetedMinMaxValues()
getFacetedMinMaxValues()
table._getFacetedUniqueValues()
getFacetedUniqueValues()
All other underscore-prefixed internals are removed. getIsSomeRowsSelected() and getIsSomePageRowsSelected() now stay true when all applicable rows are selected. Use getIsSomeRowsSelected() && !getIsAllRowsSelected() or getIsSomePageRowsSelected() && !getIsAllPageRowsSelected() for indeterminate UI.
Migration procedure
Replace React adapter imports and useReactTable; remove Table-only preact/compat configuration.
Inventory used features, row models, registries, state slices, methods, and _ internals.
Build tableFeatures() in prerequisite order; remove getCoreRowModel and old row-model placement.
Apply all pinning, sizing, sorting, row, and selection mappings above.
Update helpers/types with typeof features, retarget augmentation, and prefer feature registry/meta slots.
Replace getState()/onStateChange; explicitly choose internal, per-slice controlled, or external-atom ownership.
Replace unbound row/cell/column/header methods and migrate rendering.
Add tableOptions, subscriptions, or createTableHook only where reusable composition or render isolation needs them.
Type-check and exercise every enabled client- and server-side flow.
Audit away stockFeatures if the production table should remain tree-shakable.
Final migration checklist
Replace React-adapter imports/useReactTable with native Preact imports/useTable and remove Table-only compat aliases.
Register every used stock feature explicitly and put prerequisites before dependent slots.
Remove getCoreRowModel; move all eight optional row-model factories into tableFeatures.
Move filterFns, sortFns, aggregationFns, and filterMeta into feature slots.
Replace table.getState() and top-level onStateChange; choose Preact selectors, Subscribe, per-slice callbacks, store subscription, or external atoms.
Audit controlled/external ownership and atom precedence.
Replace unbound or copied row/cell/column/header methods.
Apply the entire left/right to start/end pinning map and logical sticky CSS.
Split pinning options and sizing/resizing; rename resizing state, setter, and callback.
Apply every sorting option/API/type/registry rename.
Remove all listed underscore-prefixed internals and use public replacements.
Pair each some-selected check with its all-selected predicate for indeterminate UI.
Update TFeatures helpers/types, columns(), StockFeatures, meta slots, registries, and RowData.
Migrate Preact FlexRender and adopt tableOptions/createTableHook only where useful.
Type-check and test every enabled client/manual feature plus LTR/RTL layout behavior.
Remove temporary stockFeatures after the explicit-feature audit when tree-shaking matters.
Common migration failures
Keeping React imports or compatibility aliases after adopting the native Preact adapter.
Registering a row model without its feature, or leaving it on table options.
Assuming table.atoms narrows parent renders while the default full selector is still active.
Supplying controlled state without its per-slice change handler.
Destructuring prototype-backed methods.
Renaming pinning state without updating sticky CSS and all region APIs.
Using the changed “some selected” semantics directly as checkbox indeterminate state.
API discovery
Inspect node_modules/@tanstack/preact-table/dist/index.d.ts and node_modules/@tanstack/table-core/dist/index.d.ts. Do not copy React adapter APIs merely because the v8 app used preact/compat.
1---2name: tanstack-preact-table-migrate-v8-to-v93description: Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits.4license: MIT5---67Read `@tanstack/table-core#migrate-v8-to-v9`, `getting-started`, and `table-state`. Use this as the exhaustive Preact migration checklist. Verify the exact v9 APIs in the installed declarations before assuming a different v9 version has the same surface.89Framework prerequisite: Preact 10 or newer (`preact >=10`).1011## Target architecture1213```tsx14import {15 createSortedRowModel,16 rowSortingFeature,17 tableFeatures,18 useTable,19} from '@tanstack/preact-table'2021const features = tableFeatures({22 rowSortingFeature,23 sortedRowModel: createSortedRowModel(),24})25const table = useTable({ features, columns, data })26```2728V8 did not have a first-party Preact adapter; many Preact apps used `@tanstack/react-table` through `preact/compat`. V9 uses native `@tanstack/preact-table`. Remove compatibility aliases that existed only for Table after replacing the imports. Prefer explicit features as the end state; `stockFeatures` is only a kitchen-sink migration shortcut.2930## Complete breaking-change map3132### Adapter, construction, and features3334| v8 | v9 |35| --------------------------------------------------------- | ------------------------------------------------------------------- |36| `@tanstack/react-table` through `preact/compat` | Native `@tanstack/preact-table` |37| `useReactTable(options)` | `useTable({ ...options, features })` |38| Every feature bundled | Register used `*Feature` objects with `tableFeatures()` |39| `getCoreRowModel()` option | Remove it; core is automatic |40| `getFilteredRowModel()` | `filteredRowModel: createFilteredRowModel()` |41| `getSortedRowModel()` | `sortedRowModel: createSortedRowModel()` |42| `getPaginationRowModel()` | `paginatedRowModel: createPaginatedRowModel()` |43| `getExpandedRowModel()` | `expandedRowModel: createExpandedRowModel()` |44| `getGroupedRowModel()` | `groupedRowModel: createGroupedRowModel()` |45| `getFacetedRowModel()` | `facetedRowModel: createFacetedRowModel()` |46| `getFacetedMinMaxValues()` | `facetedMinMaxValues: createFacetedMinMaxValues()` |47| `getFacetedUniqueValues()` | `facetedUniqueValues: createFacetedUniqueValues()` |48| Table/factory `sortingFns`, `filterFns`, `aggregationFns` | `sortFns`, `filterFns`, `aggregationFns` slots in `tableFeatures()` |49| Removed `rowModels: { ... }` object | Direct named slots in `tableFeatures()` |5051In the registry slots, register individually imported built-ins (`filterFn_includesString`, `sortFn_alphanumeric`, `aggregationFn_sum`, and so on) under their conventional keys alongside custom functions; the full `filterFns`/`sortFns`/`aggregationFns` registry objects still work but bundle every built-in.5253Register each prerequisite feature before its row-model slot. Stock features are `cellSelectionFeature`, `columnFilteringFeature`, `globalFilteringFeature`, `rowSortingFeature`, `rowPaginationFeature`, `rowSelectionFeature`, `rowExpandingFeature`, `rowPinningFeature`, `columnPinningFeature`, `columnVisibilityFeature`, `columnOrderingFeature`, `columnSizingFeature`, `columnResizingFeature`, `rowAggregationFeature`, `columnGroupingFeature`, and `columnFacetingFeature`. Aggregation is independent from grouping: register `rowAggregationFeature` for aggregation APIs and add `columnGroupingFeature` only for grouped rows.5455### Preact state and subscriptions5657| v8 | v9 |58| --------------------------- | ------------------------------------------------------------------------------------ |59| `table.getState()` | Reactive `table.state`, snapshot `table.store.state`, or `table.atoms.<slice>.get()` |60| Top-level `onStateChange` | Per-slice callbacks or `table.store.subscribe()` |61| Whole-component state reads | Custom second-argument selector, `table.Subscribe`, or atom source |6263The default `useTable` selector subscribes the component to all registered state. Pass a selector for `table.state`, or `() => null` and subscribe lower:6465```tsx66const table = useTable(options, () => null)6768<table.Subscribe source={table.atoms.rowSelection}>69 {selection => <span>{Object.keys(selection).length} selected</span>}70</table.Subscribe>71```7273Controlled `state` plus per-slice `on[State]Change` remains supported. For app-owned atoms, use `useCreateAtom`/`useSelector` from `@tanstack/preact-store` and pass them through `options.atoms`. An external atom wins over `state` for the same slice; do not mirror both ownership models.7475### Rendering and composition7677- Replace React-adapter `flexRender(def, context)` with `<table.FlexRender cell={cell} />` or standalone `<FlexRender ... />`; the function remains for advanced cases.78- Use `tableOptions()` for typed reusable option fragments.79- Use `createTableHook({ features, ...defaults })` for repeated app table conventions; it returns native app helpers such as `useAppTable` and `createAppColumnHelper`.80- Call row/cell/column/header methods on their instance. Prototype methods lose `this` when destructured and do not appear in object spread, `Object.keys`, or JSON. Table-instance methods are not affected.8182### TypeScript and helpers8384| v8 | v9 |85| ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |86| `createColumnHelper<Person>()` | `createColumnHelper<typeof features, Person>()` |87| Plain arrays that widen nested values | `columnHelper.columns([...])` |88| `ColumnDef<TData>` | `ColumnDef<TFeatures, TData, TValue>` |89| `Column<TData>`, `Row<TData>`, `Table<TData>` | Add `TFeatures` first |90| `Cell<TData, TValue>` | `Cell<TFeatures, TData, TValue>` |91| React module augmentation | Target `@tanstack/preact-table` |92| `TableMeta<TData>` / `ColumnMeta<TData, TValue>` | Add `TFeatures`, or use per-table `tableMeta` / `columnMeta` slots with `metaHelper()` |93| Global `FilterFns`, `SortFns`, `AggregationFns`, and `FilterMeta` augmentation | `filterFns`, `sortFns`, `aggregationFns`, and `filterMeta` feature slots |94| `RowData = unknown` | Record or array row data |9596Infer with `typeof features`; use `StockFeatures` only for deliberate `stockFeatures` typing.9798### Shared API and semantic changes99100| v8 pinning | v9 |101| -------------------------------------------------------------- | ------------------------------------------------------------- |102| `columnPinning.left` / `.right` | `.start` / `.end` |103| `column.pin('left' \| 'right')` | `column.pin('start' \| 'end')` |104| `getIsPinned() === 'left' \| 'right'` | `'start' \| 'end'` |105| `row.getLeftVisibleCells()` / `getRightVisibleCells()` | `getStartVisibleCells()` / `getEndVisibleCells()` |106| `getLeftHeaderGroups()` / `getRightHeaderGroups()` | `getStartHeaderGroups()` / `getEndHeaderGroups()` |107| `getLeftFooterGroups()` / `getRightFooterGroups()` | `getStartFooterGroups()` / `getEndFooterGroups()` |108| `getLeftFlatHeaders()` / `getRightFlatHeaders()` | `getStartFlatHeaders()` / `getEndFlatHeaders()` |109| `getLeftLeafHeaders()` / `getRightLeafHeaders()` | `getStartLeafHeaders()` / `getEndLeafHeaders()` |110| `getLeftLeafColumns()` / `getRightLeafColumns()` | `getStartLeafColumns()` / `getEndLeafColumns()` |111| `getLeftVisibleLeafColumns()` / `getRightVisibleLeafColumns()` | `getStartVisibleLeafColumns()` / `getEndVisibleLeafColumns()` |112| `getLeftTotalSize()` / `getRightTotalSize()` | `getStartTotalSize()` / `getEndTotalSize()` |113| `column.getStart('left')` | `column.getStart('start')` |114| `column.getAfter('right')` | `column.getAfter('end')` |115| `column.getIndex('left' \| 'right')` | `column.getIndex('start' \| 'end')` |116117Logical pinning does not style DOM direction automatically; use logical CSS insets. `columnResizeDirection` is unchanged.118119| v8 | v9 |120| ----------------------------------------------- | -------------------------------------------------------------------------- |121| Table `enablePinning` | `enableColumnPinning` plus `enableRowPinning`; column-level option remains |122| Combined sizing/resizing | `columnSizingFeature` plus `columnResizingFeature` for drag resizing |123| `columnSizingInfo` / `onColumnSizingInfoChange` | `columnResizing` / `onColumnResizingChange` |124| `setColumnSizingInfo()` | `setColumnResizing()` |125| `sortingFn` / `sortingFns` | `sortFn` / `sortFns` |126| `getSortingFn()` / `getAutoSortingFn()` | `getSortFn()` / `getAutoSortFn()` |127| `SortingFn` / `SortingFns` | `SortFn` / `SortFns` |128| `row._getAllCellsByColumnId()` | `row.getAllCellsByColumnId()` |129| `table._getPinnedRows()` | `getTopRows()`, `getCenterRows()`, or `getBottomRows()` |130| `table._getFacetedRowModel()` | Public faceting APIs on the relevant column/table |131| `table._getFacetedMinMaxValues()` | `getFacetedMinMaxValues()` |132| `table._getFacetedUniqueValues()` | `getFacetedUniqueValues()` |133134All other underscore-prefixed internals are removed. `getIsSomeRowsSelected()` and `getIsSomePageRowsSelected()` now stay true when all applicable rows are selected. Use `getIsSomeRowsSelected() && !getIsAllRowsSelected()` or `getIsSomePageRowsSelected() && !getIsAllPageRowsSelected()` for indeterminate UI.135136## Migration procedure1371381. Replace React adapter imports and `useReactTable`; remove Table-only `preact/compat` configuration.1392. Inventory used features, row models, registries, state slices, methods, and `_` internals.1403. Build `tableFeatures()` in prerequisite order; remove `getCoreRowModel` and old row-model placement.1414. Apply all pinning, sizing, sorting, row, and selection mappings above.1425. Update helpers/types with `typeof features`, retarget augmentation, and prefer feature registry/meta slots.1436. Replace `getState()`/`onStateChange`; explicitly choose internal, per-slice controlled, or external-atom ownership.1447. Replace unbound row/cell/column/header methods and migrate rendering.1458. Add `tableOptions`, subscriptions, or `createTableHook` only where reusable composition or render isolation needs them.1469. Type-check and exercise every enabled client- and server-side flow.14710. Audit away `stockFeatures` if the production table should remain tree-shakable.148149## Final migration checklist150151- [ ] Replace React-adapter imports/useReactTable with native Preact imports/useTable and remove Table-only compat aliases.152- [ ] Register every used stock feature explicitly and put prerequisites before dependent slots.153- [ ] Remove `getCoreRowModel`; move all eight optional row-model factories into `tableFeatures`.154- [ ] Move `filterFns`, `sortFns`, `aggregationFns`, and `filterMeta` into feature slots.155- [ ] Replace `table.getState()` and top-level `onStateChange`; choose Preact selectors, Subscribe, per-slice callbacks, store subscription, or external atoms.156- [ ] Audit controlled/external ownership and atom precedence.157- [ ] Replace unbound or copied row/cell/column/header methods.158- [ ] Apply the entire left/right to start/end pinning map and logical sticky CSS.159- [ ] Split pinning options and sizing/resizing; rename resizing state, setter, and callback.160- [ ] Apply every sorting option/API/type/registry rename.161- [ ] Remove all listed underscore-prefixed internals and use public replacements.162- [ ] Pair each some-selected check with its all-selected predicate for indeterminate UI.163- [ ] Update TFeatures helpers/types, `columns()`, `StockFeatures`, meta slots, registries, and RowData.164- [ ] Migrate Preact FlexRender and adopt tableOptions/createTableHook only where useful.165- [ ] Type-check and test every enabled client/manual feature plus LTR/RTL layout behavior.166- [ ] Remove temporary stockFeatures after the explicit-feature audit when tree-shaking matters.167168## Common migration failures169170- Keeping React imports or compatibility aliases after adopting the native Preact adapter.171- Registering a row model without its feature, or leaving it on table options.172- Assuming `table.atoms` narrows parent renders while the default full selector is still active.173- Supplying controlled state without its per-slice change handler.174- Destructuring prototype-backed methods.175- Renaming pinning state without updating sticky CSS and all region APIs.176- Using the changed “some selected” semantics directly as checkbox indeterminate state.177178## API discovery179180Inspect `node_modules/@tanstack/preact-table/dist/index.d.ts` and `node_modules/@tanstack/table-core/dist/index.d.ts`. Do not copy React adapter APIs merely because the v8 app used `preact/compat`.
Run npx skillmds@latest add lukasa1993/tanstack-preact-table-migrate-v8-to-v9 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Perform a complete Preact TanStack Table v8-to-v9 migration: remove React-through-preact/compat usage, adopt the native adapter, explicit features and row-model slots, Preact state and atoms, rendering, composition, types, and every shared API rename and semantic change. Use for migration plans, implementation, or audits. It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free. This skill is licensed under MIT.
lukasa1993 (@lukasa1993) published this skill. Their other Agent Skills are listed on their SkillMD profile.