@tanstack/vue-table 9.0.0-beta.40 → 9.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,393 +0,0 @@
1
- ---
2
- name: vue/migrate-v8-to-v9
3
- description: >
4
- Mechanical breaking-change migration from `@tanstack/vue-table` v8 to v9. Rename `useVueTable`
5
- → `useTable`, move `getCoreRowModel`/`getSortedRowModel`/etc. options into the `tableFeatures`
6
- object as row model factory slots, add the mandatory `features` via `tableFeatures({...})`,
7
- update `createColumnHelper<TData>()` → `createColumnHelper<typeof features, TData>()`, rename
8
- `sortingFn`/`sortingFns` → `sortFn`/`sortFns`, swap `table.getState()` for `table.state`
9
- / `table.state` / `table.atoms.<slice>.get()`, and prefer `<FlexRender :cell="cell" />` over
10
- the legacy `:render`/`:props` shape. Vue has NO `/legacy` entrypoint — migration is a direct
11
- rewrite. The Vue adapter installs `vueReactivity()` automatically.
12
- type: lifecycle
13
- library: tanstack-table
14
- framework: vue
15
- library_version: '9.0.0-alpha.48'
16
- requires:
17
- - setup
18
- - state-management
19
- - column-definitions
20
- sources:
21
- - docs/framework/vue/guide/migrating.md
22
- - docs/framework/vue/vue-table.md
23
- - docs/framework/vue/guide/table-state.md
24
- - packages/vue-table/src/useTable.ts
25
- - packages/vue-table/src/FlexRender.ts
26
- ---
27
-
28
- # Migrate @tanstack/vue-table v8 → v9
29
-
30
- ## Dependencies
31
-
32
- ```bash
33
- pnpm add @tanstack/vue-table@latest
34
- # Optional, for external atoms during/after migration:
35
- pnpm add @tanstack/vue-store
36
- ```
37
-
38
- **Vue has no `/legacy` entrypoint.** The `useLegacyTable` React shim is React-only — Vue
39
- projects rewrite each table directly. The good news: most call sites are mechanical renames.
40
-
41
- ## Setup — the v8 vs v9 shape side-by-side
42
-
43
- ### v8 (before)
44
-
45
- ```vue
46
- <script setup lang="ts">
47
- import {
48
- FlexRender,
49
- createColumnHelper,
50
- getCoreRowModel,
51
- getSortedRowModel,
52
- getPaginationRowModel,
53
- useVueTable,
54
- } from '@tanstack/vue-table'
55
- import type { ColumnDef } from '@tanstack/vue-table'
56
-
57
- const columnHelper = createColumnHelper<Person>()
58
- const columns: ColumnDef<Person, any>[] = [
59
- columnHelper.accessor('age', { header: 'Age', sortingFn: 'alphanumeric' }),
60
- ]
61
-
62
- const table = useVueTable({
63
- columns,
64
- data,
65
- getCoreRowModel: getCoreRowModel(),
66
- getSortedRowModel: getSortedRowModel(),
67
- getPaginationRowModel: getPaginationRowModel(),
68
- })
69
- </script>
70
-
71
- <template>
72
- <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
73
- </template>
74
- ```
75
-
76
- ### v9 (after)
77
-
78
- ```vue
79
- <script setup lang="ts">
80
- import {
81
- FlexRender,
82
- createColumnHelper,
83
- createPaginatedRowModel,
84
- createSortedRowModel,
85
- rowPaginationFeature,
86
- rowSortingFeature,
87
- sortFns,
88
- tableFeatures,
89
- useTable,
90
- } from '@tanstack/vue-table'
91
-
92
- const features = tableFeatures({
93
- rowSortingFeature,
94
- rowPaginationFeature,
95
- sortedRowModel: createSortedRowModel(),
96
- paginatedRowModel: createPaginatedRowModel(),
97
- sortFns,
98
- })
99
- const columnHelper = createColumnHelper<typeof features, Person>()
100
- const columns = columnHelper.columns([
101
- columnHelper.accessor('age', { header: 'Age', sortFn: 'alphanumeric' }), // sortingFn → sortFn
102
- ])
103
-
104
- const table = useTable({
105
- features,
106
- columns,
107
- data,
108
- })
109
- </script>
110
-
111
- <template>
112
- <!-- Preferred shorthand. Legacy :render / :props still compiles. -->
113
- <FlexRender :cell="cell" />
114
- </template>
115
- ```
116
-
117
- ## Rename Cheat-Sheet
118
-
119
- | v8 | v9 |
120
- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
121
- | `useVueTable(opts)` | `useTable(opts, selector?)` |
122
- | `getCoreRowModel: getCoreRowModel()` | implicit; not an option |
123
- | `getSortedRowModel: getSortedRowModel()` | `tableFeatures({ sortedRowModel: createSortedRowModel(), sortFns })` |
124
- | `getFilteredRowModel: getFilteredRowModel()` | `tableFeatures({ filteredRowModel: createFilteredRowModel(), filterFns })` |
125
- | `getPaginationRowModel: getPaginationRowModel()` | `tableFeatures({ paginatedRowModel: createPaginatedRowModel() })` |
126
- | `getGroupedRowModel: getGroupedRowModel()` | `tableFeatures({ groupedRowModel: createGroupedRowModel(), aggregationFns })` |
127
- | `createColumnHelper<TData>()` | `createColumnHelper<typeof features, TData>()` |
128
- | `ColumnDef<TData, TValue>` | `ColumnDef<TFeatures, TData, TValue>` |
129
- | `Column<TData, TValue>` / `Row<TData>` / `Cell<TData, TValue>` | `…<TFeatures, TData, TValue>` |
130
- | `sortingFn` (column def) | `sortFn` |
131
- | `sortingFns` | `sortFns` |
132
- | `getSortingFn()` / `getAutoSortingFn()` | `getSortFn()` / `getAutoSortFn()` |
133
- | `SortingFn` / `SortingFns` types | `SortFn` / `SortFns` |
134
- | `enablePinning: true` | `enableColumnPinning` and/or `enableRowPinning` |
135
- | `state.columnSizingInfo` | `state.columnResizing` |
136
- | `onColumnSizingInfoChange` | `onColumnResizingChange` |
137
- | `ColumnSizing` feature | `columnSizingFeature` + `columnResizingFeature` (split) |
138
- | `table.getState()` | `table.state` (full) / `table.state` (selector) / `table.atoms.<slice>.get()` |
139
- | `row._getAllCellsByColumnId()` | `row.getAllCellsByColumnId()` (underscore removed) |
140
- | `table._getFacetedRowModel()` / `_getFacetedMinMaxValues()` / `_getFacetedUniqueValues()` | Same names without leading underscore |
141
- | `<FlexRender :render="…" :props="…" />` | `<FlexRender :cell="cell" />` / `:header` / `:footer` (preferred; legacy still works) |
142
-
143
- Source: `docs/framework/vue/guide/migrating.md`, `docs/framework/react/guide/migrating.md` (the
144
- non-Vue-specific renames are shared across adapters).
145
-
146
- ## Core Patterns
147
-
148
- ### 1. Convert `getXRowModel` options to features-slot factories
149
-
150
- Row model factories now live on the `tableFeatures({...})` object alongside the feature
151
- they belong to. The `*Fns` registries move there too — this is what makes them tree-shakeable
152
- in v9.
153
-
154
- ```ts
155
- const features = tableFeatures({
156
- rowSortingFeature,
157
- columnFilteringFeature,
158
- rowPaginationFeature,
159
- rowGroupingFeature,
160
- rowExpandingFeature,
161
- sortedRowModel: createSortedRowModel(),
162
- filteredRowModel: createFilteredRowModel(),
163
- paginatedRowModel: createPaginatedRowModel(),
164
- groupedRowModel: createGroupedRowModel(),
165
- expandedRowModel: createExpandedRowModel(),
166
- sortFns,
167
- filterFns,
168
- aggregationFns,
169
- })
170
- ```
171
-
172
- The `*Fns` registries are open-ended; do not cite a number of built-in fns.
173
-
174
- ### 2. Add `tableFeatures` and register every feature you use
175
-
176
- If a feature isn't in `features`, its API isn't on the table (TS error AND runtime
177
- `undefined`). This is v9's biggest behavioral change.
178
-
179
- ```ts
180
- import {
181
- columnFilteringFeature,
182
- rowPaginationFeature,
183
- rowSortingFeature,
184
- rowSelectionFeature,
185
- tableFeatures,
186
- } from '@tanstack/vue-table'
187
-
188
- const features = tableFeatures({
189
- rowSortingFeature,
190
- rowPaginationFeature,
191
- columnFilteringFeature,
192
- rowSelectionFeature,
193
- })
194
- ```
195
-
196
- For a quick migration that doesn't audit usage, `stockFeatures` is the "everything" escape
197
- hatch — but it forfeits the bundle benefit. Prefer explicit registration.
198
-
199
- ### 3. Move state reads off `table.getState()`
200
-
201
- ```ts
202
- // v8
203
- const sorting = table.getState().sorting
204
-
205
- // v9 — pick the narrowest read.
206
- const sorting = table.atoms.sorting.get() // narrowest, no full state object built
207
- const snapshot = table.state // full readonly view
208
- const table = useTable(opts, (s) => ({ sorting: s.sorting })) // selected reactive state
209
- table.state.sorting // typed selector output
210
- ```
211
-
212
- For Vue reactivity, wrap an atom read in `computed`:
213
-
214
- ```ts
215
- const sorting = computed(() => table.atoms.sorting.get())
216
- ```
217
-
218
- ### 4. v8 controlled state still works — just use getters
219
-
220
- v8 controlled state via `state` + `on[State]Change` is preserved in v9 for migration paths.
221
- The Vue-specific rule is that each `state.<slice>` must be a **getter** so Vue tracks `.value`.
222
-
223
- ```ts
224
- const sorting = ref<SortingState>([])
225
-
226
- const table = useTable({
227
- features,
228
- columns,
229
- data,
230
- state: {
231
- get sorting() {
232
- return sorting.value
233
- }, // ← getter, not raw ref
234
- },
235
- onSortingChange: (u) => {
236
- sorting.value = typeof u === 'function' ? u(sorting.value) : u
237
- },
238
- })
239
- ```
240
-
241
- For new code, prefer `atoms: { sorting: sortingAtom }` from `@tanstack/vue-store` — no
242
- `on[State]Change` plumbing required. See `tanstack-table/vue/compose-with-tanstack-store`.
243
-
244
- ### 5. Update `<FlexRender>` shape
245
-
246
- ```vue
247
- <!-- v8 (still works in v9, but verbose) -->
248
- <FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
249
-
250
- <!-- v9 preferred -->
251
- <FlexRender :cell="cell" />
252
- <FlexRender :header="header" />
253
- <FlexRender :footer="header" />
254
- ```
255
-
256
- Source: `packages/vue-table/src/FlexRender.ts`.
257
-
258
- ## Common Mistakes
259
-
260
- ### Importing `useVueTable` (CRITICAL)
261
-
262
- ```ts
263
- // ❌
264
- import { useVueTable } from '@tanstack/vue-table'
265
-
266
- // ✅
267
- import { useTable } from '@tanstack/vue-table'
268
- ```
269
-
270
- `useVueTable` is not exported. Migration is a rename.
271
-
272
- ### Trying to use `useLegacyTable` in Vue (CRITICAL)
273
-
274
- `useLegacyTable` is a React-only shim from `@tanstack/react-table/legacy`. There is no Vue
275
- equivalent. If you have many tables to convert, do them one file at a time — the renames are
276
- mechanical.
277
-
278
- ### Passing `getCoreRowModel: getCoreRowModel()` as an option (CRITICAL)
279
-
280
- ```ts
281
- // ❌ v8 muscle memory.
282
- const table = useTable({
283
- features,
284
- columns,
285
- data,
286
- getCoreRowModel: getCoreRowModel(),
287
- })
288
-
289
- // ✅ Core row model is implicit. No `rowModels` option needed.
290
- const table = useTable({ features, columns, data })
291
- ```
292
-
293
- ### Forgetting `features` (CRITICAL)
294
-
295
- `features` is required even for a no-features migration. Pass `tableFeatures({})` for empty,
296
- or list everything you use (features + row model factories + fn registries). Without it:
297
- `'features' is missing in type`.
298
-
299
- ### Wrong `createColumnHelper` generic arity (CRITICAL)
300
-
301
- ```ts
302
- // ❌ v8
303
- const columnHelper = createColumnHelper<Person>()
304
-
305
- // ✅ v9
306
- const columnHelper = createColumnHelper<typeof features, Person>()
307
- ```
308
-
309
- Same applies to type annotations: `ColumnDef<typeof features, Person>`,
310
- `Row<typeof features, Person>`, `Cell<typeof features, Person, unknown>`.
311
-
312
- ### Forgetting to register `sortFns` / `filterFns` in the features object (CRITICAL)
313
-
314
- ```ts
315
- // ❌ Runtime: no sort fns registered, sort is a no-op.
316
- const features = tableFeatures({
317
- rowSortingFeature,
318
- sortedRowModel: createSortedRowModel(),
319
- })
320
-
321
- // ✅ Register the fn map as a slot on the features object.
322
- const features = tableFeatures({
323
- rowSortingFeature,
324
- sortedRowModel: createSortedRowModel(),
325
- sortFns,
326
- })
327
- ```
328
-
329
- ### Missed `sortingFn` → `sortFn` rename (HIGH)
330
-
331
- ```ts
332
- // ❌ v8 name on a column def. TypeScript will complain.
333
- columnHelper.accessor('age', { header: 'Age', sortingFn: 'alphanumeric' })
334
-
335
- // ✅
336
- columnHelper.accessor('age', { header: 'Age', sortFn: 'alphanumeric' })
337
- ```
338
-
339
- ### `enablePinning: true` (HIGH)
340
-
341
- Split in v9 — pick one or both:
342
-
343
- ```ts
344
- useTable({
345
- // ...
346
- enableColumnPinning: true,
347
- enableRowPinning: true,
348
- })
349
- ```
350
-
351
- ### Reading `table.getState()` everywhere (HIGH)
352
-
353
- The method was removed. Replace each call site explicitly — the right substitute depends on
354
- the read pattern (see "Move state reads off `table.getState()`" above).
355
-
356
- ### Underscore-prefixed APIs (HIGH)
357
-
358
- ```ts
359
- row._getAllCellsByColumnId() // ❌
360
- row.getAllCellsByColumnId() // ✅
361
-
362
- table._getFacetedRowModel() // ❌
363
- table.getFacetedRowModel() // ✅
364
- ```
365
-
366
- Drop the underscore on every former internal API.
367
-
368
- ### Bundling `stockFeatures` and calling it done (HIGH)
369
-
370
- `stockFeatures` is the "ship everything" escape hatch — useful during the rename pass to keep
371
- things compiling, but if you don't go back and replace it with an explicit `tableFeatures({...})`
372
- that lists only what you actually render, you forfeit v9's tree-shaking — which is one of the
373
- main reasons to migrate.
374
-
375
- ### Hallucinating pre-v9 names (CRITICAL — top AI tell)
376
-
377
- `useTable` from `react-table` v7, `useVueTable` from v8, `getCoreRowModel()` as an option,
378
- single-generic `createColumnHelper<TData>()`, `sortingFn`, `enablePinning`, `table.getState()` —
379
- all v7/v8 shapes. None of them compile in v9.
380
-
381
- ### Reimplementing what built-in APIs already provide (CRITICAL — #1 AI tell)
382
-
383
- Migration is a great time to delete hand-rolled sort/filter/select state machines and route
384
- through `table.setSorting`, `table.setColumnFilters`, `row.toggleSelected`, `table.nextPage`,
385
- etc.
386
-
387
- ## See Also
388
-
389
- - `tanstack-table/vue/getting-started` — the v9 minimum-viable shape
390
- - `tanstack-table/vue/table-state` — reactivity model + reading state
391
- - `tanstack-table/vue/production-readiness` — finish the migration with bundle + identity audits
392
- - `tanstack-table/table-core/setup` — `features` / row model factory slots deep dive
393
- - `tanstack-table/table-core/column-definitions` — column helper + generics
@@ -1,278 +0,0 @@
1
- ---
2
- name: vue/production-readiness
3
- description: >
4
- Ship-ready optimizations for `@tanstack/vue-table` v9. Tree-shake by listing ONLY the
5
- `features` you actually render — never default to `stockFeatures`. Keep `features`,
6
- `columnHelper`, and `columns` at module scope for stable identity (Vue re-evaluates the
7
- `<script setup>` block per component instance — declare these outside or in a const). Row
8
- model factories and fn registries live as slots on the `tableFeatures({...})` object, not as
9
- a separate `rowModels` option. Use the `useTable(opts, selector)` second argument or per-slice
10
- `computed(() => table.atoms.<slice>.get())` / `useSelector(table.atoms.<slice>)` from
11
- `@tanstack/vue-store` to narrow re-renders. Vue's reactivity already evaluates only dirty
12
- computed deps, so `table.Subscribe` is NOT the React-Compiler workaround it is in React;
13
- reach for fine-grained reads (`computed`, `useSelector`) first.
14
- type: lifecycle
15
- library: tanstack-table
16
- framework: vue
17
- library_version: '9.0.0-alpha.48'
18
- requires:
19
- - setup
20
- - state-management
21
- - vue/table-state
22
- sources:
23
- - docs/guide/features.md
24
- - docs/framework/vue/guide/table-state.md
25
- - packages/vue-table/src/useTable.ts
26
- - packages/vue-table/src/reactivity.ts
27
- ---
28
-
29
- # Production Readiness (Vue)
30
-
31
- ## Dependencies
32
-
33
- ```bash
34
- pnpm add @tanstack/vue-table @tanstack/vue-store
35
- ```
36
-
37
- `@tanstack/vue-store` is required if you want to use `useSelector` for per-slice atom
38
- subscriptions outside the table.
39
-
40
- ## Setup — the production-ready shape
41
-
42
- ```ts
43
- // table-setup.ts — module scope, stable identity for everyone.
44
- import {
45
- createPaginatedRowModel,
46
- createSortedRowModel,
47
- rowPaginationFeature,
48
- rowSortingFeature,
49
- sortFns,
50
- tableFeatures,
51
- } from '@tanstack/vue-table'
52
-
53
- export const features = tableFeatures({
54
- rowSortingFeature,
55
- rowPaginationFeature,
56
- sortedRowModel: createSortedRowModel(),
57
- paginatedRowModel: createPaginatedRowModel(),
58
- sortFns,
59
- })
60
- ```
61
-
62
- ```vue
63
- <script setup lang="ts">
64
- import { ref } from 'vue'
65
- import { useTable } from '@tanstack/vue-table'
66
- import { features } from './table-setup'
67
- import { columns } from './columns'
68
-
69
- const props = defineProps<{ data: Person[] }>()
70
- const data = ref(props.data)
71
-
72
- // Narrow selector — table.state only carries what we actually render at this level.
73
- const table = useTable({ features, columns, data }, (state) => ({
74
- pagination: state.pagination,
75
- sorting: state.sorting,
76
- }))
77
- </script>
78
- ```
79
-
80
- ## Core Patterns
81
-
82
- ### 1. Tree-shake by listing only the features you render
83
-
84
- ```ts
85
- // ❌ Ships filtering, faceting, grouping, pinning, expanding, sizing, resizing,
86
- // visibility, ordering, row-selection, row-pinning code you don't use.
87
- const features = tableFeatures(stockFeatures)
88
-
89
- // ✅ A sort-only table ships ~6–7 kb of feature code instead of ~15–20 kb.
90
- const features = tableFeatures({ rowSortingFeature })
91
- ```
92
-
93
- `stockFeatures` exists for migrations and prototyping. Audit before shipping — if a feature
94
- isn't wired into a column def or UI handler, drop it. v9 is tree-shakeable specifically so you
95
- only pay for what you use.
96
-
97
- ### 2. Module-scope `features` / `columnHelper` / `columns`
98
-
99
- ```ts
100
- // ❌ Inside <script setup> — every component instance creates new identities, which churns
101
- // the table's option-watcher and triggers a `setOptions` on every reactive tick.
102
- const features = tableFeatures({
103
- rowSortingFeature,
104
- sortedRowModel: createSortedRowModel(),
105
- sortFns,
106
- })
107
- const columnHelper = createColumnHelper<typeof features, Person>()
108
- const columns = columnHelper.columns([
109
- /* ... */
110
- ])
111
- ```
112
-
113
- ```ts
114
- // ✅ Module scope — one identity, shared across instances, GC-friendly.
115
- const features = tableFeatures({
116
- rowSortingFeature,
117
- sortedRowModel: createSortedRowModel(),
118
- sortFns,
119
- })
120
- const columnHelper = createColumnHelper<typeof features, Person>()
121
- export const columns = columnHelper.columns([
122
- /* ... */
123
- ])
124
- ```
125
-
126
- `data` is the exception: a stable `ref` is fine — the adapter watches `.value`. Just don't
127
- write `data: ref([...])` inline in the table options (a fresh ref each render).
128
-
129
- ### 3. Narrow the `useTable` selector
130
-
131
- ```ts
132
- // ❌ Default selector — table.state contains every registered slice; any state change re-renders
133
- // every consumer of table.state.
134
- const table = useTable({ features, columns, data })
135
-
136
- // ✅ Project only what THIS component renders. Other slices still drive table internals; you
137
- // just don't subscribe to them at the component level.
138
- const table = useTable({ features, columns, data }, (state) => ({
139
- pagination: state.pagination,
140
- sorting: state.sorting,
141
- }))
142
- ```
143
-
144
- The selector is a TanStack Store selector — its compare is shallow by default, so identity
145
- stable returns avoid re-evaluation.
146
-
147
- ### 4. Per-slice subscriptions for nested components
148
-
149
- When only a leaf component cares about one slice, subscribe to the atom directly. This is
150
- narrower than `useTable`'s selector (which still touches the full store).
151
-
152
- ```vue
153
- <!-- SelectedCount.vue -->
154
- <script setup lang="ts">
155
- import { useSelector } from '@tanstack/vue-store'
156
- import type { TableProp } from './types'
157
-
158
- const props = defineProps<TableProp>()
159
- // Re-renders only when rowSelection changes.
160
- const selection = useSelector(props.table.atoms.rowSelection)
161
- </script>
162
-
163
- <template>
164
- <span>{{ Object.keys(selection).length }} selected</span>
165
- </template>
166
- ```
167
-
168
- Or with `computed` (no `vue-store` peer required, but only reactive in a Vue scope):
169
-
170
- ```ts
171
- const selectedCount = computed(
172
- () => Object.keys(table.atoms.rowSelection.get()).length,
173
- )
174
- ```
175
-
176
- ### 5. Vue does NOT need `table.Subscribe` the way React does
177
-
178
- `table.Subscribe` is a render-prop component primarily useful in React when the React Compiler
179
- can't see through the table closure. Vue's reactivity already re-evaluates dependent reads
180
- automatically — `computed`, `useSelector`, and the `useTable` selector cover the same ground
181
- with less indirection.
182
-
183
- Use `table.Subscribe` only for cross-store source subscription (`<table.Subscribe :source="..."
184
- :selector="..."`) — and even then, `useSelector(source, selector)` is usually cleaner.
185
-
186
- ### 6. Stress patterns
187
-
188
- - **Virtualize rows / columns** — pair with `@tanstack/vue-virtual`. Keep `useVirtualizer` in
189
- the deepest possible component (`TableBody`, not `App`) so unrelated state changes don't
190
- re-run it. See `tanstack-table/vue/compose-with-tanstack-virtual`.
191
- - **Debounce filter / resize writes** — see `tanstack-table/vue/compose-with-tanstack-pacer`.
192
- - **Hoist heavy column defs out of `<script setup>`** — into a `columns.ts` module.
193
-
194
- ## Common Mistakes
195
-
196
- ### Using `stockFeatures` in production without auditing (HIGH)
197
-
198
- Defeats the entire reason v9 is tree-shakeable. Replace with an explicit
199
- `tableFeatures({...})` listing only the features your UI renders.
200
-
201
- ### Re-declaring `features` / `columns` inside `<script setup>` (HIGH)
202
-
203
- Vue runs `<script setup>` once per component instance, but that's still per-page-navigation in
204
- SPAs and per-instance in any reusable component. Module scope is cheapest and shared.
205
-
206
- ### Inline `data: ref([])` or `data: computed(() => something ?? [])` in options (MEDIUM)
207
-
208
- The `?? []` pattern creates a fresh array identity on every recompute. Either:
209
-
210
- ```ts
211
- // ✅ stable empty array
212
- const EMPTY: Person[] = []
213
- const data = computed(() => dataQuery.data.value?.rows ?? EMPTY)
214
-
215
- // ✅ or hold a ref that's only updated on real data changes
216
- const data = ref<Person[]>([])
217
- watchEffect(() => {
218
- const next = dataQuery.data.value?.rows
219
- if (next && next !== data.value) data.value = next
220
- })
221
- ```
222
-
223
- ### Leaving the default selector when only one slice is rendered (MEDIUM)
224
-
225
- ```ts
226
- // ❌ Whole table re-evaluates table.state on every keystroke in a column filter.
227
- const table = useTable({ features, columns, data })
228
-
229
- // ✅
230
- const table = useTable(opts, (s) => ({ pagination: s.pagination }))
231
- ```
232
-
233
- ### Premature `table.Subscribe` everywhere (MEDIUM)
234
-
235
- For a 50-row table, wrapping every header in `<table.Subscribe>` adds complexity without
236
- measurable benefit. Vue's default reactivity is already fine-grained — reach for `Subscribe` /
237
- selectors only when you've measured a hotspot.
238
-
239
- Maintainer guidance: advanced state-management patterns are for advanced cases.
240
-
241
- ### Reading `table.state` in a deep component when only that component cares (MEDIUM)
242
-
243
- If only `SelectedCount.vue` cares about `rowSelection`, prefer
244
- `useSelector(table.atoms.rowSelection)` over reading `table.state.rowSelection` at a parent
245
- and prop-drilling.
246
-
247
- ### Hoisting `useTable` above a virtualizer (HIGH)
248
-
249
- If a parent component reads `table.state` reactively, every state change re-renders the
250
- parent, which re-runs `useVirtualizer` and blows away scroll position. Keep
251
- `useVirtualizer` inside the leaf component that needs it; pass `table` (which is stable) as a
252
- prop. See `tanstack-table/vue/compose-with-tanstack-virtual`.
253
-
254
- ### Hallucinating pre-v9 API names (CRITICAL)
255
-
256
- `stockFeatures` exists in v9; `useVueTable` does not; `getCoreRowModel()` is not a valid
257
- option in v9. If you're optimizing v8 code, you're optimizing the wrong codebase — see
258
- `tanstack-table/vue/migrate-v8-to-v9` first.
259
-
260
- ### "API missing" because feature not registered (CRITICAL — v9-specific)
261
-
262
- You can't tree-shake a feature you never added. If `table.setSorting` is `undefined`, the
263
- solution isn't a workaround — it's `tableFeatures({ rowSortingFeature })`. Optimization
264
- follows correctness.
265
-
266
- ### Reimplementing built-in transitions (CRITICAL — #1 AI tell)
267
-
268
- `table.setSorting`, `row.toggleSelected`, `table.nextPage`, `column.setFilterValue` — all
269
- exist for nearly every state transition. Hand-rolled state machines are slower (skip
270
- internal invariants) and harder to maintain (reset APIs, multi-sort, etc. don't apply).
271
-
272
- ## See Also
273
-
274
- - `tanstack-table/vue/table-state` — selector, atoms, FlexRender deep dive
275
- - `tanstack-table/vue/getting-started` — the simple shape you're now optimizing
276
- - `tanstack-table/vue/compose-with-tanstack-virtual` — virtualization for stress cases
277
- - `tanstack-table/vue/compose-with-tanstack-pacer` — debounce filter / throttle resize writes
278
- - `tanstack-table/vue/compose-with-tanstack-store` — external atoms in depth