@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.
- package/README.md +1 -0
- package/package.json +2 -2
- package/skills/create-table-hook/SKILL.md +146 -0
- package/skills/getting-started/SKILL.md +144 -0
- package/skills/migrate-v8-to-v9/SKILL.md +185 -0
- package/skills/table-state/SKILL.md +191 -0
- package/skills/with-tanstack-query/SKILL.md +127 -0
- package/skills/with-tanstack-virtual/SKILL.md +116 -0
- package/skills/vue/client-to-server/SKILL.md +0 -360
- package/skills/vue/compose-with-tanstack-form/SKILL.md +0 -369
- package/skills/vue/compose-with-tanstack-pacer/SKILL.md +0 -321
- package/skills/vue/compose-with-tanstack-query/SKILL.md +0 -383
- package/skills/vue/compose-with-tanstack-store/SKILL.md +0 -302
- package/skills/vue/compose-with-tanstack-virtual/SKILL.md +0 -344
- package/skills/vue/getting-started/SKILL.md +0 -415
- package/skills/vue/migrate-v8-to-v9/SKILL.md +0 -393
- package/skills/vue/production-readiness/SKILL.md +0 -278
- package/skills/vue/table-state/SKILL.md +0 -399
|
@@ -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
|