@tanstack/vue-table 9.0.0-beta.5 → 9.0.0-beta.50

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.
Files changed (48) hide show
  1. package/README.md +2 -0
  2. package/dist/FlexRender.cjs +2 -3
  3. package/dist/FlexRender.cjs.map +1 -1
  4. package/dist/FlexRender.js +2 -3
  5. package/dist/FlexRender.js.map +1 -1
  6. package/dist/createTableHook.cjs +4 -9
  7. package/dist/createTableHook.cjs.map +1 -1
  8. package/dist/createTableHook.d.cts +39 -12
  9. package/dist/createTableHook.d.ts +39 -12
  10. package/dist/createTableHook.js +4 -9
  11. package/dist/createTableHook.js.map +1 -1
  12. package/dist/experimental-worker-plugin.cjs +9 -0
  13. package/dist/experimental-worker-plugin.d.cts +1 -0
  14. package/dist/experimental-worker-plugin.d.ts +1 -0
  15. package/dist/experimental-worker-plugin.js +3 -0
  16. package/dist/index.d.cts +2 -2
  17. package/dist/index.d.ts +2 -2
  18. package/dist/reactivity.cjs +2 -6
  19. package/dist/reactivity.cjs.map +1 -1
  20. package/dist/reactivity.js +2 -6
  21. package/dist/reactivity.js.map +1 -1
  22. package/dist/useTable.cjs +3 -7
  23. package/dist/useTable.cjs.map +1 -1
  24. package/dist/useTable.d.cts +1 -8
  25. package/dist/useTable.d.ts +1 -8
  26. package/dist/useTable.js +3 -7
  27. package/dist/useTable.js.map +1 -1
  28. package/package.json +8 -4
  29. package/skills/create-table-hook/SKILL.md +146 -0
  30. package/skills/getting-started/SKILL.md +144 -0
  31. package/skills/migrate-v8-to-v9/SKILL.md +185 -0
  32. package/skills/table-state/SKILL.md +191 -0
  33. package/skills/with-tanstack-query/SKILL.md +127 -0
  34. package/skills/with-tanstack-virtual/SKILL.md +116 -0
  35. package/src/createTableHook.ts +108 -17
  36. package/src/experimental-worker-plugin.ts +1 -0
  37. package/src/reactivity.ts +1 -2
  38. package/src/useTable.ts +3 -11
  39. package/skills/vue/client-to-server/SKILL.md +0 -365
  40. package/skills/vue/compose-with-tanstack-form/SKILL.md +0 -369
  41. package/skills/vue/compose-with-tanstack-pacer/SKILL.md +0 -318
  42. package/skills/vue/compose-with-tanstack-query/SKILL.md +0 -385
  43. package/skills/vue/compose-with-tanstack-store/SKILL.md +0 -301
  44. package/skills/vue/compose-with-tanstack-virtual/SKILL.md +0 -340
  45. package/skills/vue/getting-started/SKILL.md +0 -409
  46. package/skills/vue/migrate-v8-to-v9/SKILL.md +0 -375
  47. package/skills/vue/production-readiness/SKILL.md +0 -271
  48. package/skills/vue/table-state/SKILL.md +0 -403
@@ -1,385 +0,0 @@
1
- ---
2
- name: vue/compose-with-tanstack-query
3
- description: >
4
- Server-side / async data flow for `@tanstack/vue-table` v9 + `@tanstack/vue-query`. Key the
5
- `useQuery` query on the table state that drives the request (pagination + sorting + filters).
6
- Set `manualPagination` / `manualSorting` / `manualFiltering` for slices the server owns.
7
- Drop the matching `rowModels` factory (don't ship `paginatedRowModel` when the server
8
- paginates). Pass `rowCount` so `table.getPageCount()` works without all rows in memory. Pass
9
- `placeholderData: keepPreviousData` to avoid a "0 rows flash" between pages. The cleanest
10
- wiring uses external `createAtom`s + `options.atoms` so the table writes through to the atom
11
- and the query refetches with no `on[State]Change` plumbing. The canonical Vue example is at
12
- `examples/vue/with-tanstack-query/`.
13
- type: composition
14
- library: tanstack-table
15
- framework: vue
16
- library_version: '9.0.0-alpha.48'
17
- requires:
18
- - vue/client-to-server
19
- - pagination
20
- - state-management
21
- sources:
22
- - examples/vue/with-tanstack-query/src/App.tsx
23
- - examples/vue/with-tanstack-query/src/fetchData.ts
24
- - examples/vue/with-tanstack-query/src/useService.ts
25
- ---
26
-
27
- # Compose @tanstack/vue-table with @tanstack/vue-query
28
-
29
- ## Dependencies
30
-
31
- ```bash
32
- pnpm add @tanstack/vue-table @tanstack/vue-query @tanstack/vue-store
33
- ```
34
-
35
- `@tanstack/vue-store` is the recommended way to own server-controlled state slices
36
- (pagination / sorting / global filter). It removes the `on[State]Change` plumbing — the table
37
- writes through to your atom, and the query refetches because the atom drives `queryKey`.
38
-
39
- ## Setup — paginated server table
40
-
41
- ```vue
42
- <script setup lang="ts">
43
- import { computed, ref, watchEffect } from 'vue'
44
- import { createAtom, useSelector } from '@tanstack/vue-store'
45
- import { keepPreviousData, useQuery } from '@tanstack/vue-query'
46
- import {
47
- FlexRender,
48
- createColumnHelper,
49
- rowPaginationFeature,
50
- tableFeatures,
51
- useTable,
52
- type PaginationState,
53
- } from '@tanstack/vue-table'
54
- import { fetchPeople } from './fetchData'
55
-
56
- type Person = { firstName: string; lastName: string; age: number }
57
-
58
- const features = tableFeatures({ rowPaginationFeature })
59
- const columnHelper = createColumnHelper<typeof features, Person>()
60
- const columns = columnHelper.columns([
61
- columnHelper.accessor('firstName', { header: 'First' }),
62
- columnHelper.accessor('age', { header: 'Age' }),
63
- ])
64
-
65
- // 1) Own pagination state in an atom.
66
- const paginationAtom = createAtom<PaginationState>({
67
- pageIndex: 0,
68
- pageSize: 10,
69
- })
70
- const pagination = useSelector(paginationAtom)
71
-
72
- // 2) Vue Query: function-returning-options form ensures reactivity over pagination.value.
73
- const dataQuery = useQuery(() => ({
74
- queryKey: ['people', pagination.value],
75
- queryFn: () => fetchPeople(pagination.value),
76
- placeholderData: keepPreviousData,
77
- }))
78
-
79
- // 3) Stable empty array — avoid `?? []` inline in options.
80
- const EMPTY: Person[] = []
81
- const tableData = computed<Person[]>(() => dataQuery.data.value?.rows ?? EMPTY)
82
-
83
- // 4) Hold last known total so the pager doesn't reset during refetches.
84
- const rowCount = ref(0)
85
- watchEffect(() => {
86
- const next = dataQuery.data.value?.rowCount
87
- if (next != null) rowCount.value = next
88
- })
89
-
90
- // 5) Manual pagination. No paginatedRowModel — server paginates.
91
- const table = useTable({
92
- features,
93
- rowModels: {},
94
- columns,
95
- data: tableData,
96
- rowCount,
97
- atoms: { pagination: paginationAtom },
98
- manualPagination: true,
99
- // NOTE: no `onPaginationChange` — table.setPageIndex writes through to paginationAtom.
100
- })
101
- </script>
102
-
103
- <template>
104
- <div>
105
- <table>
106
- <thead>
107
- <tr v-for="hg in table.getHeaderGroups()" :key="hg.id">
108
- <th v-for="h in hg.headers" :key="h.id">
109
- <FlexRender v-if="!h.isPlaceholder" :header="h" />
110
- </th>
111
- </tr>
112
- </thead>
113
- <tbody>
114
- <tr v-for="row in table.getRowModel().rows" :key="row.id">
115
- <td v-for="cell in row.getAllCells()" :key="cell.id">
116
- <FlexRender :cell="cell" />
117
- </td>
118
- </tr>
119
- </tbody>
120
- </table>
121
-
122
- <button
123
- @click="table.previousPage()"
124
- :disabled="!table.getCanPreviousPage()"
125
- >
126
- ‹
127
- </button>
128
- <span
129
- >Page {{ pagination.pageIndex + 1 }} of {{ table.getPageCount() }}</span
130
- >
131
- <button @click="table.nextPage()" :disabled="!table.getCanNextPage()">
132
- ›
133
- </button>
134
- <span v-if="dataQuery.isFetching.value">Loading…</span>
135
- </div>
136
- </template>
137
- ```
138
-
139
- Source: `examples/vue/with-tanstack-query/src/App.tsx`.
140
-
141
- ## Core Patterns
142
-
143
- ### 1. `useQuery` accepts a getter for reactive keys
144
-
145
- ```ts
146
- // ✅ The function form re-evaluates whenever its reactive reads (pagination.value) change.
147
- const dataQuery = useQuery(() => ({
148
- queryKey: ['people', pagination.value],
149
- queryFn: () => fetchPeople(pagination.value),
150
- placeholderData: keepPreviousData,
151
- }))
152
-
153
- // ❌ Object form with raw value — captured once, never refetches on pagination changes.
154
- const dataQuery = useQuery({
155
- queryKey: ['people', pagination.value],
156
- queryFn: () => fetchPeople(pagination.value),
157
- })
158
- ```
159
-
160
- Vue Query's reactive options require the function form. This is unlike React Query's object
161
- form — a common transcription error from copy-pasting React examples.
162
-
163
- ### 2. Why external atoms beat `state` + `on*Change` for Query
164
-
165
- External atoms collapse two flows into one:
166
-
167
- ```ts
168
- // Atom flow
169
- table.nextPage() // table writes to atom
170
- → paginationAtom.set( ... )
171
- → useSelector ref updates
172
- → queryKey changes
173
- → useQuery refetches
174
- ```
175
-
176
- ```ts
177
- // state + on*Change flow (still supported; works fine)
178
- table.nextPage()
179
- → onPaginationChange(updater)
180
- → setLocalPagination(updater)
181
- → ref updates
182
- → queryKey changes
183
- → useQuery refetches
184
- ```
185
-
186
- Same end-to-end, but the atom version doesn't need `onPaginationChange` plumbing and a
187
- sibling component can `useSelector(paginationAtom)` directly without prop-drilling.
188
-
189
- ### 3. Multiple server-controlled slices
190
-
191
- ```ts
192
- const sortingAtom = createAtom<SortingState>([])
193
- const paginationAtom = createAtom<PaginationState>({
194
- pageIndex: 0,
195
- pageSize: 10,
196
- })
197
- const filtersAtom = createAtom<ColumnFiltersState>([])
198
-
199
- const sorting = useSelector(sortingAtom)
200
- const pagination = useSelector(paginationAtom)
201
- const filters = useSelector(filtersAtom)
202
-
203
- const dataQuery = useQuery(() => ({
204
- queryKey: [
205
- 'people',
206
- {
207
- sorting: sorting.value,
208
- pagination: pagination.value,
209
- filters: filters.value,
210
- },
211
- ],
212
- queryFn: () =>
213
- fetchPeople({
214
- sorting: sorting.value,
215
- pagination: pagination.value,
216
- filters: filters.value,
217
- }),
218
- placeholderData: keepPreviousData,
219
- }))
220
-
221
- const table = useTable({
222
- features: tableFeatures({
223
- rowSortingFeature,
224
- rowPaginationFeature,
225
- columnFilteringFeature,
226
- }),
227
- rowModels: {}, // server owns all three
228
- columns,
229
- data: computed(() => dataQuery.data.value?.rows ?? EMPTY),
230
- rowCount,
231
- atoms: {
232
- sorting: sortingAtom,
233
- pagination: paginationAtom,
234
- columnFilters: filtersAtom,
235
- },
236
- manualSorting: true,
237
- manualPagination: true,
238
- manualFiltering: true,
239
- })
240
- ```
241
-
242
- ### 4. Mutations + `invalidateQueries`
243
-
244
- ```ts
245
- import { useMutation, useQueryClient } from '@tanstack/vue-query'
246
-
247
- const queryClient = useQueryClient()
248
-
249
- const addPerson = useMutation({
250
- mutationFn: (person: Person) => savePerson(person),
251
- onSuccess: () => {
252
- queryClient.invalidateQueries({ queryKey: ['people'] })
253
- },
254
- })
255
- ```
256
-
257
- The table is a downstream consumer — it has no way to know the server data changed unless
258
- Query tells it. Always invalidate on writes.
259
-
260
- ## Common Mistakes
261
-
262
- ### Omitting `manualPagination` / `manualSorting` / `manualFiltering` (CRITICAL)
263
-
264
- ```ts
265
- // ❌ Table re-paginates the already-paginated server response.
266
- useTable({
267
- features,
268
- rowModels: {},
269
- columns,
270
- data: tableData,
271
- rowCount,
272
- atoms: { pagination: paginationAtom },
273
- // missing: manualPagination: true
274
- })
275
- ```
276
-
277
- The visible symptom is "Page 1 of 1, but I see 10 rows of a 1000-row dataset" — the table
278
- slices the 10-row page locally.
279
-
280
- ### Missing `rowCount` (CRITICAL)
281
-
282
- `getPageCount()` falls back to `Math.ceil(data.length / pageSize)` — which is `1` if the
283
- server already paginated. The pager locks at "Page 1 of 1".
284
-
285
- ### Leaving `paginatedRowModel` registered with `manualPagination` (HIGH)
286
-
287
- ```ts
288
- // ❌ Ships the factory for nothing AND it tries to paginate server-paginated data.
289
- rowModels: {
290
- paginatedRowModel: createPaginatedRowModel()
291
- }
292
-
293
- // ✅
294
- rowModels: {
295
- }
296
- ```
297
-
298
- ### Forgetting controlled state in `queryKey` (CRITICAL)
299
-
300
- ```ts
301
- // ❌ Never refetches when the user pages.
302
- useQuery(() => ({
303
- queryKey: ['people'],
304
- queryFn: () => fetchPeople(pagination.value),
305
- }))
306
-
307
- // ✅
308
- useQuery(() => ({
309
- queryKey: ['people', pagination.value],
310
- queryFn: () => fetchPeople(pagination.value),
311
- }))
312
- ```
313
-
314
- ### Skipping `placeholderData: keepPreviousData` (HIGH)
315
-
316
- Between fetches the table collapses to 0 rows, the row container collapses, scroll position
317
- jumps. `keepPreviousData` keeps the previous page visible during the refetch.
318
-
319
- ### Passing `useQuery({...})` object form with reactive deps (HIGH — Vue-specific)
320
-
321
- `@tanstack/vue-query` requires the function form `useQuery(() => ({ ... }))` for reactive
322
- options. The object form snapshots once and never re-evaluates `queryKey`.
323
-
324
- ### Inline `?? []` in `data` option (MEDIUM)
325
-
326
- ```ts
327
- // ❌ Fresh array identity every recompute → table option diff churns.
328
- data: computed(() => dataQuery.data.value?.rows ?? [])
329
-
330
- // ✅
331
- const EMPTY: Person[] = []
332
- const tableData = computed(() => dataQuery.data.value?.rows ?? EMPTY)
333
- ```
334
-
335
- ### Passing the same slice via both `state` and `atoms` (HIGH)
336
-
337
- `atoms` wins; `state` is silently ignored. Pick one mechanism per slice.
338
-
339
- ### Hallucinating React Query in a Vue project (CRITICAL — top AI tell)
340
-
341
- ```ts
342
- // ❌ React Query — wrong package for Vue.
343
- import { useQuery, keepPreviousData } from '@tanstack/react-query'
344
-
345
- // ✅
346
- import { useQuery, keepPreviousData } from '@tanstack/vue-query'
347
- ```
348
-
349
- Same hooks names, different reactivity model (function-returning-options instead of object).
350
-
351
- ### Hallucinating pre-v9 table APIs (CRITICAL)
352
-
353
- `useVueTable` + `getCoreRowModel: getCoreRowModel()` is v8. v9 uses `useTable` +
354
- `features` + `rowModels`. See `tanstack-table/vue/migrate-v8-to-v9`.
355
-
356
- ### "API missing" because feature not in `features` (CRITICAL — v9-specific)
357
-
358
- Server-side pagination still needs `rowPaginationFeature` in `tableFeatures({...})`. The
359
- feature gives you `table.nextPage` / `table.setPageIndex` / `table.getPageCount`. The
360
- `rowModels` factory is what you drop; the feature stays.
361
-
362
- ### Reimplementing pagination loop manually (CRITICAL — #1 AI tell)
363
-
364
- ```ts
365
- // ❌ Hand-rolled — bypasses table invariants.
366
- const nextPage = () => {
367
- paginationAtom.set({
368
- ...paginationAtom.get(),
369
- pageIndex: paginationAtom.get().pageIndex + 1,
370
- })
371
- }
372
-
373
- // ✅ Built-ins.
374
- table.nextPage()
375
- table.previousPage()
376
- table.setPageIndex(0)
377
- table.setPageSize(25)
378
- ```
379
-
380
- ## See Also
381
-
382
- - `tanstack-table/vue/client-to-server` — manual modes + `rowCount`
383
- - `tanstack-table/vue/compose-with-tanstack-store` — external atoms in depth
384
- - `tanstack-table/vue/table-state` — reactivity model
385
- - `tanstack-table/table-core/pagination` — `manualPagination` semantics
@@ -1,301 +0,0 @@
1
- ---
2
- name: vue/compose-with-tanstack-store
3
- description: >
4
- `@tanstack/vue-table` v9 is built on TanStack Store. Each state slice (sorting, pagination,
5
- rowSelection, columnFilters, ...) is a separate atom. Three read surfaces:
6
- `table.atoms.<slice>` (per-slice readonly), `table.store` (flat readonly view), `table.state`
7
- (selector output from `useTable`'s second argument). Two write paths: the internal
8
- `table.baseAtoms.<slice>` OR your own `options.atoms[slice]` if you opt to own it. In Vue, create
9
- atoms with `createAtom` from `@tanstack/vue-store`, read them with `useSelector` (returns a
10
- ref-like reactive value) or `computed(() => atom.get())`. The Vue adapter installs
11
- `vueReactivity()` automatically so atoms back onto `computed`/`shallowRef` and subscriptions use
12
- `watch(..., { flush: 'sync' })`. Precedence is `atoms[key]` > `state[key]` > internal baseAtom.
13
- type: composition
14
- library: tanstack-table
15
- framework: vue
16
- library_version: '9.0.0-alpha.48'
17
- requires:
18
- - state-management
19
- sources:
20
- - docs/framework/vue/guide/table-state.md
21
- - examples/vue/basic-external-atoms/
22
- - packages/vue-table/src/useTable.ts
23
- - packages/vue-table/src/reactivity.ts
24
- ---
25
-
26
- # Compose @tanstack/vue-table with @tanstack/vue-store
27
-
28
- ## Dependencies
29
-
30
- ```bash
31
- pnpm add @tanstack/vue-table @tanstack/vue-store
32
- ```
33
-
34
- `@tanstack/vue-store` provides `createAtom`, `useSelector`, and the `shallow` comparator. The
35
- Vue table adapter is built on TanStack Store internally — this skill is about exposing that
36
- machinery to your own app code via external atoms.
37
-
38
- ## Setup — own a slice, share it across components
39
-
40
- ```vue
41
- <script setup lang="ts">
42
- import { ref } from 'vue'
43
- import { createAtom, useSelector } from '@tanstack/vue-store'
44
- import {
45
- FlexRender,
46
- createColumnHelper,
47
- createPaginatedRowModel,
48
- createSortedRowModel,
49
- rowPaginationFeature,
50
- rowSortingFeature,
51
- sortFns,
52
- tableFeatures,
53
- useTable,
54
- type PaginationState,
55
- type SortingState,
56
- } from '@tanstack/vue-table'
57
-
58
- type Person = { firstName: string; lastName: string; age: number }
59
-
60
- const features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
61
- const columnHelper = createColumnHelper<typeof features, Person>()
62
- const columns = columnHelper.columns([
63
- columnHelper.accessor('firstName', { header: 'First' }),
64
- columnHelper.accessor('age', { header: 'Age' }),
65
- ])
66
-
67
- // 1) Module / setup scope — stable atom identity. createAtom returns the same atom every call;
68
- // do NOT recreate it inside a watcher or render fn.
69
- const sortingAtom = createAtom<SortingState>([])
70
- const paginationAtom = createAtom<PaginationState>({
71
- pageIndex: 0,
72
- pageSize: 10,
73
- })
74
-
75
- // 2) Reactive reads anywhere — sibling components can call useSelector on the same atom.
76
- const sorting = useSelector(sortingAtom)
77
- const pagination = useSelector(paginationAtom)
78
-
79
- const data = ref<Person[]>([])
80
-
81
- // 3) Pass via `options.atoms`. NO `onSortingChange` / `onPaginationChange` needed —
82
- // `table.setSorting()` writes through to your atom.
83
- const table = useTable({
84
- features,
85
- rowModels: {
86
- sortedRowModel: createSortedRowModel(sortFns),
87
- paginatedRowModel: createPaginatedRowModel(),
88
- },
89
- columns,
90
- data,
91
- atoms: {
92
- sorting: sortingAtom,
93
- pagination: paginationAtom,
94
- },
95
- })
96
- </script>
97
-
98
- <template>
99
- <div>
100
- Page {{ pagination.pageIndex + 1 }}, sorted by
101
- {{ sorting[0]?.id ?? 'none' }}
102
- </div>
103
- </template>
104
- ```
105
-
106
- Source: `examples/vue/basic-external-atoms/src/App.tsx`.
107
-
108
- ## Core Patterns
109
-
110
- ### 1. The three read surfaces — pick the narrowest
111
-
112
- ```ts
113
- // (a) Atom — narrowest. Reads/writes one slice.
114
- table.atoms.sorting.get()
115
- table.atoms.sorting.set([{ id: 'age', desc: true }])
116
- useSelector(table.atoms.sorting) // reactive ref-like
117
- computed(() => table.atoms.sorting.get()) // alternative
118
-
119
- // (b) Flat store — full snapshot.
120
- table.state // readonly
121
- table.state.sorting // current value
122
-
123
- // (c) useTable selector — typed reactive projection.
124
- const table = useTable(opts, (s) => ({ sorting: s.sorting }))
125
- table.state.sorting
126
- ```
127
-
128
- `table.atoms.<slice>` only contains slices for features registered in `features`. If
129
- `rowSortingFeature` is not registered, `table.atoms.sorting` is `undefined`.
130
-
131
- ### 2. Atom precedence rules
132
-
133
- Per slice, the resolution order is:
134
-
135
- ```
136
- options.atoms[slice] > options.state[slice] > table.baseAtoms[slice]
137
- (external, you own) (controlled state) (internal default)
138
- ```
139
-
140
- ```ts
141
- // ❌ Passing both for the SAME slice. `atoms.sorting` wins; `state.sorting` is silently dead.
142
- useTable({
143
- features,
144
- rowModels: {},
145
- columns,
146
- data,
147
- state: {
148
- get sorting() {
149
- return localSorting.value
150
- },
151
- }, // ignored
152
- onSortingChange: setLocalSorting, // ignored
153
- atoms: { sorting: sortingAtom }, // wins
154
- })
155
- ```
156
-
157
- Pick **one mechanism per slice**. Different slices can use different mechanisms freely.
158
-
159
- ### 3. When to choose external atoms vs `state` + `on[State]Change`
160
-
161
- External atoms are better when:
162
-
163
- - A sibling component (sidebar, header, breadcrumbs) needs to read the same slice without going
164
- through `table`.
165
- - You want to persist the slice (localStorage, URL params) — write a `watch(atom, persist)`
166
- outside the table.
167
- - You're integrating with TanStack Query and want `queryKey: ['x', useSelector(atom).value]`
168
- to drive refetches without `on*Change` plumbing.
169
-
170
- `state` + `on[State]Change` (with **getter wrappers** on each slice — Vue-specific) is fine
171
- when:
172
-
173
- - You're migrating from v8 and want the smallest diff.
174
- - The slice is already a `ref` you don't want to convert.
175
-
176
- ### 4. Reset is YOUR responsibility for owned atoms
177
-
178
- ```ts
179
- // ❌ table.reset() does NOT clear external atoms — it only resets internally-owned slices.
180
- table.reset()
181
-
182
- // ✅ Reset your atoms yourself.
183
- sortingAtom.set([])
184
- paginationAtom.set({ pageIndex: 0, pageSize: 10 })
185
- table.reset() // optional — for any slices the table owns internally
186
- ```
187
-
188
- Same applies to per-slice resets: `table.resetSorting()` will update through the atom because
189
- the atom is the slice's source of truth — but a base-atom reset (`table.resetSorting(true)`)
190
- only resets internal default state, not your owned atom. Read the source if you're chaining
191
- resets across mixed ownership.
192
-
193
- ### 5. Persisting an atom outside the table
194
-
195
- ```ts
196
- import { watch } from 'vue'
197
-
198
- const sortingAtom = createAtom<SortingState>(
199
- JSON.parse(localStorage.getItem('mySort') ?? '[]'),
200
- )
201
- const sorting = useSelector(sortingAtom)
202
- watch(sorting, (s) => localStorage.setItem('mySort', JSON.stringify(s)))
203
- ```
204
-
205
- The table now writes through `table.setSorting()` → `sortingAtom.set(...)` → the watcher
206
- persists. No `on*Change` involved.
207
-
208
- ## Common Mistakes
209
-
210
- ### Creating atoms inside a render fn or component body without stable identity (CRITICAL)
211
-
212
- ```vue
213
- <script setup>
214
- // ❌ A new atom every component setup → the table re-binds every mount → "state resets"
215
- // feels random to the user. Only an issue when atoms are created in a reactive scope.
216
- const sortingAtom = computed(() => createAtom < SortingState > [])
217
- </script>
218
- ```
219
-
220
- ```vue
221
- <script setup>
222
- // ✅ Setup scope is once per component instance — fine.
223
- const sortingAtom = createAtom < SortingState > []
224
-
225
- // ✅ Module scope — shared across instances.
226
- // (declare at top of file outside <script setup>)
227
- </script>
228
- ```
229
-
230
- ### Passing the same slice via both `state` and `atoms` (HIGH)
231
-
232
- `atoms` wins; the `state` plumbing is silently dead. Pick one mechanism per slice.
233
-
234
- ### Pairing `atoms.sorting` with `onSortingChange` (MEDIUM)
235
-
236
- ```ts
237
- // ❌ Confusing — the handler never fires usefully because the atom is the writeback.
238
- useTable({
239
- // ...
240
- atoms: { sorting: sortingAtom },
241
- onSortingChange: (u) => {
242
- /* dead */
243
- },
244
- })
245
- ```
246
-
247
- When using `atoms` for a slice, drop the matching `on[State]Change`. Subscribe to the atom
248
- itself (`watch(useSelector(sortingAtom), ...)` or `subscribe`) if you need side effects on change.
249
-
250
- ### Expecting `table.reset()` to clear external atoms (HIGH)
251
-
252
- ```ts
253
- // ❌
254
- table.reset() // does not touch your atoms
255
-
256
- // ✅ Reset what you own.
257
- sortingAtom.set([])
258
- paginationAtom.set({ pageIndex: 0, pageSize: 10 })
259
- ```
260
-
261
- ### Reading `table.state.<slice>` in a deeply-nested component when only that slice matters (MEDIUM)
262
-
263
- ```ts
264
- // ❌ All of table.state is computed and tracked even if the component only cares about rowSelection.
265
- const table = inject(TABLE)
266
- const count = computed(() => Object.keys(table.state.rowSelection).length)
267
-
268
- // ✅ Subscribe to the atom directly.
269
- const selection = useSelector(table.atoms.rowSelection)
270
- const count = computed(() => Object.keys(selection.value).length)
271
- ```
272
-
273
- ### Reading `atom.get()` outside a reactive scope (HIGH — Vue-specific)
274
-
275
- ```ts
276
- // ❌ One-shot read; doesn't track.
277
- const sorting = sortingAtom.get()
278
-
279
- // ✅ useSelector returns a reactive ref-like.
280
- const sorting = useSelector(sortingAtom)
281
-
282
- // ✅ or wrap in computed.
283
- const sorting = computed(() => sortingAtom.get())
284
- ```
285
-
286
- ### Hallucinating pre-v9 API names (CRITICAL)
287
-
288
- `useVueTable`, `table.getState()` — both v8. v9 uses `useTable` and `table.state` /
289
- `table.state` / `table.atoms.<slice>.get()`. See `tanstack-table/vue/migrate-v8-to-v9`.
290
-
291
- ### "API missing" because feature not in `features` (CRITICAL — v9-specific)
292
-
293
- `table.atoms.sorting` is `undefined` unless `rowSortingFeature` is registered. The fix isn't to
294
- fall back to `state` — it's to add the feature to `tableFeatures({...})`.
295
-
296
- ## See Also
297
-
298
- - `tanstack-table/vue/table-state` — read surfaces and selector reactivity
299
- - `tanstack-table/vue/client-to-server` — atoms + manual modes wiring
300
- - `tanstack-table/vue/compose-with-tanstack-query` — atoms in the queryKey
301
- - `tanstack-table/vue/production-readiness` — `useSelector` per-slice for narrow re-renders