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.
67
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Read @tanstack/table-core#migrate-v8-to-v9, getting-started, and table-state. Use this as the exhaustive Preact migration checklist. Verify exact APIs in the installed declarations before assuming another v9 beta has the same surface.
Framework prerequisite: Preact 10 or newer (preact >=10).
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.
| 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() |
getCoreRowModel() option | Remove it; core is automatic |
getFilteredRowModel() | filteredRowModel: createFilteredRowModel() |
getSortedRowModel() | sortedRowModel: createSortedRowModel() |
getPaginationRowModel() | paginatedRowModel: createPaginatedRowModel() |
getExpandedRowModel() | expandedRowModel: createExpandedRowModel() |
getGroupedRowModel() | groupedRowModel: createGroupedRowModel() |
getFacetedRowModel() | facetedRowModel: createFacetedRowModel() |
getFacetedMinMaxValues() | facetedMinMaxValues: createFacetedMinMaxValues() |
getFacetedUniqueValues() | facetedUniqueValues: createFacetedUniqueValues() |
Table/factory sortingFns, filterFns, aggregationFns | sortFns, filterFns, aggregationFns slots in tableFeatures() |
Early-beta rowModels: { ... } | 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.
| 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:
const table = useTable(options, () => null)
<table.Subscribe source={table.atoms.rowSelection}>
{selection => <span>{Object.keys(selection).length} selected</span>}
</table.Subscribe>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.
flexRender(def, context) with <table.FlexRender cell={cell} /> or standalone <FlexRender ... />; the function remains for advanced cases.tableOptions() for typed reusable option fragments.createTableHook({ features, ...defaults }) for repeated app table conventions; it returns native app helpers such as useAppTable and createAppColumnHelper.this when destructured and do not appear in object spread, Object.keys, or JSON. Table-instance methods are not affected.| 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.
| v8 / pre-beta.38 pinning | v9 beta.38+ |
|---|---|
columnPinning.left / .right | .start / .end |
column.pin('left' | 'right') | column.pin('start' | 'end') |
getIsPinned() === 'left' | 'right' | 'start' | 'end' |
row.getLeftVisibleCells() / getRightVisibleCells() | getStartVisibleCells() / getEndVisibleCells() |
getLeftHeaderGroups() / getRightHeaderGroups() | getStartHeaderGroups() / getEndHeaderGroups() |
getLeftFooterGroups() / getRightFooterGroups() | getStartFooterGroups() / getEndFooterGroups() |
getLeftFlatHeaders() / getRightFlatHeaders() | getStartFlatHeaders() / getEndFlatHeaders() |
getLeftLeafHeaders() / getRightLeafHeaders() | getStartLeafHeaders() / getEndLeafHeaders() |
getLeftLeafColumns() / getRightLeafColumns() | getStartLeafColumns() / getEndLeafColumns() |
getLeftVisibleLeafColumns() / getRightVisibleLeafColumns() | getStartVisibleLeafColumns() / getEndVisibleLeafColumns() |
getLeftTotalSize() / getRightTotalSize() | getStartTotalSize() / getEndTotalSize() |
column.getStart('left') | column.getStart('start') |
column.getAfter('right') | column.getAfter('end') |
column.getIndex('left' | 'right') | column.getIndex('start' | 'end') |
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.
useReactTable; remove Table-only preact/compat configuration._ internals.tableFeatures() in prerequisite order; remove getCoreRowModel and old/early-beta row-model placement.typeof features, retarget augmentation, and prefer feature registry/meta slots.getState()/onStateChange; explicitly choose internal, per-slice controlled, or external-atom ownership.tableOptions, subscriptions, or createTableHook only where reusable composition or render isolation needs them.stockFeatures if the production table should remain tree-shakable.getCoreRowModel; move all eight optional row-model factories into tableFeatures.filterFns, sortFns, aggregationFns, and filterMeta into feature slots.table.getState() and top-level onStateChange; choose Preact selectors, Subscribe, per-slice callbacks, store subscription, or external atoms.columns(), StockFeatures, meta slots, registries, and RowData.table.atoms narrows parent renders while the default full selector is still active.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.
9e523bc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.