@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.
- package/README.md +2 -0
- package/dist/FlexRender.cjs +2 -3
- package/dist/FlexRender.cjs.map +1 -1
- package/dist/FlexRender.js +2 -3
- package/dist/FlexRender.js.map +1 -1
- package/dist/createTableHook.cjs +4 -9
- package/dist/createTableHook.cjs.map +1 -1
- package/dist/createTableHook.d.cts +39 -12
- package/dist/createTableHook.d.ts +39 -12
- package/dist/createTableHook.js +4 -9
- package/dist/createTableHook.js.map +1 -1
- package/dist/experimental-worker-plugin.cjs +9 -0
- package/dist/experimental-worker-plugin.d.cts +1 -0
- package/dist/experimental-worker-plugin.d.ts +1 -0
- package/dist/experimental-worker-plugin.js +3 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/reactivity.cjs +2 -6
- package/dist/reactivity.cjs.map +1 -1
- package/dist/reactivity.js +2 -6
- package/dist/reactivity.js.map +1 -1
- package/dist/useTable.cjs +3 -7
- package/dist/useTable.cjs.map +1 -1
- package/dist/useTable.d.cts +1 -8
- package/dist/useTable.d.ts +1 -8
- package/dist/useTable.js +3 -7
- package/dist/useTable.js.map +1 -1
- package/package.json +8 -4
- 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/src/createTableHook.ts +108 -17
- package/src/experimental-worker-plugin.ts +1 -0
- package/src/reactivity.ts +1 -2
- package/src/useTable.ts +3 -11
- package/skills/vue/client-to-server/SKILL.md +0 -365
- package/skills/vue/compose-with-tanstack-form/SKILL.md +0 -369
- package/skills/vue/compose-with-tanstack-pacer/SKILL.md +0 -318
- package/skills/vue/compose-with-tanstack-query/SKILL.md +0 -385
- package/skills/vue/compose-with-tanstack-store/SKILL.md +0 -301
- package/skills/vue/compose-with-tanstack-virtual/SKILL.md +0 -340
- package/skills/vue/getting-started/SKILL.md +0 -409
- package/skills/vue/migrate-v8-to-v9/SKILL.md +0 -375
- package/skills/vue/production-readiness/SKILL.md +0 -271
- package/skills/vue/table-state/SKILL.md +0 -403
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: vue/compose-with-tanstack-virtual
|
|
3
|
-
description: >
|
|
4
|
-
`@tanstack/vue-table` v9 does not include virtualization — pair with `@tanstack/vue-virtual`.
|
|
5
|
-
Standard row-virtualization pattern: get the row array from `table.getRowModel().rows`, feed
|
|
6
|
-
`rows.length` to `useVirtualizer(computed(() => ({ count, estimateSize, getScrollElement,
|
|
7
|
-
overscan })))`, iterate `rowVirtualizer.value.getVirtualItems()` instead of `rows.map`,
|
|
8
|
-
absolute-position each row with `transform: translateY(virtualRow.start)px`, and use
|
|
9
|
-
`display: grid` on `<table>`/`<thead>`/`<tbody>`. The virtualizer MUST live in the deepest
|
|
10
|
-
component possible so unrelated state changes don't re-run it. Column virtualization mirrors
|
|
11
|
-
the shape with `horizontal: true` plus padding-left/right placeholder cells. Skip
|
|
12
|
-
`measureElement` on Firefox — it returns inconsistent table-row heights.
|
|
13
|
-
type: composition
|
|
14
|
-
library: tanstack-table
|
|
15
|
-
framework: vue
|
|
16
|
-
library_version: '9.0.0-alpha.48'
|
|
17
|
-
requires:
|
|
18
|
-
- vue/table-state
|
|
19
|
-
- row-expanding
|
|
20
|
-
sources:
|
|
21
|
-
- docs/guide/virtualization.md
|
|
22
|
-
- examples/vue/virtualized-rows/src/App.vue
|
|
23
|
-
- examples/vue/virtualized-columns/
|
|
24
|
-
- examples/vue/virtualized-infinite-scrolling/
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
# Compose @tanstack/vue-table with @tanstack/vue-virtual
|
|
28
|
-
|
|
29
|
-
## Dependencies
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pnpm add @tanstack/vue-table @tanstack/vue-virtual
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
`@tanstack/vue-virtual` is the Vue adapter for TanStack Virtual. Its key Vue-isms:
|
|
36
|
-
|
|
37
|
-
- `useVirtualizer(optionsRef)` accepts a `ref`/`computed` of options (or a getter), and
|
|
38
|
-
returns a `Ref<Virtualizer>` — you read `.value.getVirtualItems()`, `.value.getTotalSize()`, etc.
|
|
39
|
-
- `getScrollElement` resolves the scrollable container via a template ref.
|
|
40
|
-
|
|
41
|
-
## Setup — row virtualization, the standard pattern
|
|
42
|
-
|
|
43
|
-
```vue
|
|
44
|
-
<script setup lang="ts">
|
|
45
|
-
import { computed, ref } from 'vue'
|
|
46
|
-
import {
|
|
47
|
-
FlexRender,
|
|
48
|
-
columnSizingFeature,
|
|
49
|
-
createSortedRowModel,
|
|
50
|
-
rowSortingFeature,
|
|
51
|
-
sortFns,
|
|
52
|
-
tableFeatures,
|
|
53
|
-
useTable,
|
|
54
|
-
type ColumnDef,
|
|
55
|
-
} from '@tanstack/vue-table'
|
|
56
|
-
import { useVirtualizer } from '@tanstack/vue-virtual'
|
|
57
|
-
import { makeData, type Person } from './makeData'
|
|
58
|
-
|
|
59
|
-
const features = tableFeatures({ columnSizingFeature, rowSortingFeature })
|
|
60
|
-
|
|
61
|
-
const columns: ColumnDef<typeof features, Person>[] = [
|
|
62
|
-
{ accessorKey: 'firstName' },
|
|
63
|
-
{ accessorKey: 'lastName' },
|
|
64
|
-
{ accessorKey: 'age' },
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
const data = ref<Person[]>(makeData(50_000))
|
|
68
|
-
|
|
69
|
-
const table = useTable({
|
|
70
|
-
features,
|
|
71
|
-
rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
72
|
-
columns,
|
|
73
|
-
data,
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
const rows = computed(() => table.getRowModel().rows)
|
|
77
|
-
|
|
78
|
-
// 1) Template ref to the scrollable container.
|
|
79
|
-
const tableContainerRef = ref<HTMLDivElement | null>(null)
|
|
80
|
-
|
|
81
|
-
// 2) Reactive virtualizer options — recomputed when rows.length changes.
|
|
82
|
-
const rowVirtualizerOptions = computed(() => ({
|
|
83
|
-
count: rows.value.length,
|
|
84
|
-
estimateSize: () => 33,
|
|
85
|
-
getScrollElement: () => tableContainerRef.value,
|
|
86
|
-
overscan: 5,
|
|
87
|
-
}))
|
|
88
|
-
|
|
89
|
-
const rowVirtualizer = useVirtualizer(rowVirtualizerOptions)
|
|
90
|
-
|
|
91
|
-
const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems())
|
|
92
|
-
const totalSize = computed(() => rowVirtualizer.value.getTotalSize())
|
|
93
|
-
</script>
|
|
94
|
-
|
|
95
|
-
<template>
|
|
96
|
-
<div
|
|
97
|
-
ref="tableContainerRef"
|
|
98
|
-
:style="{ overflow: 'auto', position: 'relative', height: '800px' }"
|
|
99
|
-
>
|
|
100
|
-
<!-- display: grid is required for absolute-positioned virtual rows -->
|
|
101
|
-
<table :style="{ display: 'grid' }">
|
|
102
|
-
<thead
|
|
103
|
-
:style="{ display: 'grid', position: 'sticky', top: 0, zIndex: 1 }"
|
|
104
|
-
>
|
|
105
|
-
<tr
|
|
106
|
-
v-for="hg in table.getHeaderGroups()"
|
|
107
|
-
:key="hg.id"
|
|
108
|
-
:style="{ display: 'flex', width: '100%' }"
|
|
109
|
-
>
|
|
110
|
-
<th
|
|
111
|
-
v-for="h in hg.headers"
|
|
112
|
-
:key="h.id"
|
|
113
|
-
:style="{ width: `${h.getSize()}px` }"
|
|
114
|
-
>
|
|
115
|
-
<FlexRender v-if="!h.isPlaceholder" :header="h" />
|
|
116
|
-
</th>
|
|
117
|
-
</tr>
|
|
118
|
-
</thead>
|
|
119
|
-
<tbody
|
|
120
|
-
:style="{
|
|
121
|
-
display: 'grid',
|
|
122
|
-
height: `${totalSize}px`,
|
|
123
|
-
position: 'relative',
|
|
124
|
-
}"
|
|
125
|
-
>
|
|
126
|
-
<tr
|
|
127
|
-
v-for="vRow in virtualRows"
|
|
128
|
-
:key="rows[vRow.index].id"
|
|
129
|
-
:data-index="vRow.index"
|
|
130
|
-
:style="{
|
|
131
|
-
display: 'flex',
|
|
132
|
-
position: 'absolute',
|
|
133
|
-
transform: `translateY(${vRow.start}px)`,
|
|
134
|
-
width: '100%',
|
|
135
|
-
}"
|
|
136
|
-
>
|
|
137
|
-
<td
|
|
138
|
-
v-for="cell in rows[vRow.index].getAllCells()"
|
|
139
|
-
:key="cell.id"
|
|
140
|
-
:style="{ display: 'flex', width: `${cell.column.getSize()}px` }"
|
|
141
|
-
>
|
|
142
|
-
<FlexRender :cell="cell" />
|
|
143
|
-
</td>
|
|
144
|
-
</tr>
|
|
145
|
-
</tbody>
|
|
146
|
-
</table>
|
|
147
|
-
</div>
|
|
148
|
-
</template>
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Source: `examples/vue/virtualized-rows/src/App.vue`.
|
|
152
|
-
|
|
153
|
-
## Core Patterns
|
|
154
|
-
|
|
155
|
-
### 1. Keep `useVirtualizer` in the deepest possible component
|
|
156
|
-
|
|
157
|
-
```vue
|
|
158
|
-
<!-- ✅ Body owns the virtualizer. Filter input changes in App.vue don't re-run it. -->
|
|
159
|
-
<!-- App.vue -->
|
|
160
|
-
<TableContainer ref="tableContainerRef">
|
|
161
|
-
<TableBody :table="table" :container-ref="tableContainerRef" />
|
|
162
|
-
</TableContainer>
|
|
163
|
-
|
|
164
|
-
<!-- TableBody.vue: useVirtualizer lives here. -->
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
This is the single most important perf rule. Putting `useVirtualizer` in the same component
|
|
168
|
-
as `useTable` means any state change in that component re-runs the virtualizer, blowing
|
|
169
|
-
scroll position and measurement cache.
|
|
170
|
-
|
|
171
|
-
### 2. `display: grid` + absolute-positioned rows
|
|
172
|
-
|
|
173
|
-
Semantic `<table>` markup still works, but the _layout_ must be CSS grid + flexbox. Without
|
|
174
|
-
`display: grid` on `<table>`/`<thead>`/`<tbody>` plus `position: absolute` + `transform:
|
|
175
|
-
translateY(start)px` on each row, virtual rows stack or overlap.
|
|
176
|
-
|
|
177
|
-
### 3. Column virtualization
|
|
178
|
-
|
|
179
|
-
```ts
|
|
180
|
-
const columnVirtualizer = useVirtualizer(
|
|
181
|
-
computed(() => ({
|
|
182
|
-
count: columns.length,
|
|
183
|
-
estimateSize: (index) => columns[index].size ?? 150,
|
|
184
|
-
getScrollElement: () => tableContainerRef.value,
|
|
185
|
-
horizontal: true,
|
|
186
|
-
overscan: 3,
|
|
187
|
-
})),
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
const virtualCols = computed(() => columnVirtualizer.value.getVirtualItems())
|
|
191
|
-
|
|
192
|
-
// Then in template, render only virtualCols. Pad with empty cells at left/right:
|
|
193
|
-
const virtualPaddingLeft = computed(() => virtualCols.value[0]?.start ?? 0)
|
|
194
|
-
const virtualPaddingRight = computed(() => {
|
|
195
|
-
const last = virtualCols.value[virtualCols.value.length - 1]
|
|
196
|
-
return columnVirtualizer.value.getTotalSize() - (last?.end ?? 0)
|
|
197
|
-
})
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Without left/right padding placeholder cells, visible columns slide left as you scroll because
|
|
201
|
-
unrendered columns aren't taking up scroll space.
|
|
202
|
-
|
|
203
|
-
### 4. Dynamic row heights via `measureElement`
|
|
204
|
-
|
|
205
|
-
```ts
|
|
206
|
-
const rowVirtualizerOptions = computed(() => ({
|
|
207
|
-
count: rows.value.length,
|
|
208
|
-
estimateSize: () => 33,
|
|
209
|
-
getScrollElement: () => tableContainerRef.value,
|
|
210
|
-
// Firefox returns inconsistent table-row heights — skip there.
|
|
211
|
-
measureElement:
|
|
212
|
-
typeof window !== 'undefined' &&
|
|
213
|
-
navigator.userAgent.indexOf('Firefox') === -1
|
|
214
|
-
? (el) => el.getBoundingClientRect().height
|
|
215
|
-
: undefined,
|
|
216
|
-
overscan: 5,
|
|
217
|
-
}))
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
In templates, wire `measureElement` via a ref callback:
|
|
221
|
-
|
|
222
|
-
```vue
|
|
223
|
-
<tr :ref="(el) => rowVirtualizer.value.measureElement(el as Element)">
|
|
224
|
-
...
|
|
225
|
-
</tr>
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### 5. Infinite scroll
|
|
229
|
-
|
|
230
|
-
Combine `useInfiniteQuery` from `@tanstack/vue-query` with a scroll-event handler that calls
|
|
231
|
-
`fetchNextPage()` when within ~500px of the bottom. **Set `manualSorting: true`** so a new
|
|
232
|
-
query fires on sort changes — otherwise the table re-sorts already-fetched pages locally and
|
|
233
|
-
scrambles order.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
const flatData = computed(
|
|
237
|
-
() => infiniteQuery.data.value?.pages.flatMap((p) => p.rows) ?? [],
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
const onScroll = (e: Event) => {
|
|
241
|
-
const t = e.target as HTMLDivElement
|
|
242
|
-
if (
|
|
243
|
-
t.scrollHeight - t.scrollTop - t.clientHeight < 500 &&
|
|
244
|
-
!infiniteQuery.isFetching.value
|
|
245
|
-
) {
|
|
246
|
-
infiniteQuery.fetchNextPage()
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Source: `examples/vue/virtualized-infinite-scrolling/`.
|
|
252
|
-
|
|
253
|
-
## Common Mistakes
|
|
254
|
-
|
|
255
|
-
### Putting `useVirtualizer` in the same component as `useTable` (CRITICAL)
|
|
256
|
-
|
|
257
|
-
Any unrelated state change in that component re-runs the virtualizer. Move it down to a
|
|
258
|
-
`TableBody.vue` component that takes the table as a prop.
|
|
259
|
-
|
|
260
|
-
### Forgetting `display: grid` on `<table>`/`<thead>`/`<tbody>` (CRITICAL)
|
|
261
|
-
|
|
262
|
-
The semantic table layout fights with absolute positioning. Virtual rows stack on top of each
|
|
263
|
-
other or overlap.
|
|
264
|
-
|
|
265
|
-
### Missing `transform: translateY(virtualRow.start)px` (CRITICAL)
|
|
266
|
-
|
|
267
|
-
All rows render at `top: 0` and only the last few are visible. The virtualizer reports the
|
|
268
|
-
correct `start` for each item; you must apply it.
|
|
269
|
-
|
|
270
|
-
### Using `measureElement` on Firefox (HIGH)
|
|
271
|
-
|
|
272
|
-
Firefox returns inconsistent border-height measurements for `<tr>` elements — rows jitter on
|
|
273
|
-
every scroll. Guard with `navigator.userAgent.indexOf('Firefox') === -1`.
|
|
274
|
-
|
|
275
|
-
### Passing options as a plain object instead of a `computed` / getter (HIGH — Vue-specific)
|
|
276
|
-
|
|
277
|
-
```ts
|
|
278
|
-
// ❌ Static options — virtualizer doesn't re-run when rows.length changes.
|
|
279
|
-
const rowVirtualizer = useVirtualizer({
|
|
280
|
-
count: rows.value.length,
|
|
281
|
-
// ...
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
// ✅ Reactive options.
|
|
285
|
-
const rowVirtualizer = useVirtualizer(
|
|
286
|
-
computed(() => ({
|
|
287
|
-
count: rows.value.length,
|
|
288
|
-
estimateSize: () => 33,
|
|
289
|
-
getScrollElement: () => tableContainerRef.value,
|
|
290
|
-
})),
|
|
291
|
-
)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Forgetting padding cells in column virtualization (HIGH)
|
|
295
|
-
|
|
296
|
-
Without `virtualPaddingLeft` / `virtualPaddingRight` cells, columns slide horizontally as you
|
|
297
|
-
scroll because unrendered columns aren't taking up scroll space.
|
|
298
|
-
|
|
299
|
-
### Forgetting `manualSorting: true` on infinite scroll (HIGH)
|
|
300
|
-
|
|
301
|
-
The table re-sorts already-fetched pages every time a new page arrives, scrambling order.
|
|
302
|
-
|
|
303
|
-
### Reading `table.state` above the virtualizer (HIGH)
|
|
304
|
-
|
|
305
|
-
Any reactive read of `table.state` in a parent component re-renders the parent → re-renders
|
|
306
|
-
the virtualizer-owning child → loses scroll. Use the narrowest read at the lowest level
|
|
307
|
-
(see `tanstack-table/vue/production-readiness`).
|
|
308
|
-
|
|
309
|
-
### Hallucinating React Virtual hooks in Vue code (CRITICAL)
|
|
310
|
-
|
|
311
|
-
```ts
|
|
312
|
-
// ❌
|
|
313
|
-
import { useVirtualizer } from '@tanstack/react-virtual'
|
|
314
|
-
|
|
315
|
-
// ✅
|
|
316
|
-
import { useVirtualizer } from '@tanstack/vue-virtual'
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
Same name, Vue-specific reactivity contract — `useVirtualizer` returns a `Ref<Virtualizer>`
|
|
320
|
-
in Vue, not a plain object.
|
|
321
|
-
|
|
322
|
-
### "API missing" — `getRowModel` returns nothing (CRITICAL — v9-specific)
|
|
323
|
-
|
|
324
|
-
If `table.getRowModel().rows` is empty when data is loaded, the row-model feature for whatever
|
|
325
|
-
slice you need (filtering/sorting/grouping) isn't registered. Add it to `tableFeatures({...})`
|
|
326
|
-
and `rowModels`.
|
|
327
|
-
|
|
328
|
-
### Reimplementing virtualization manually (CRITICAL — #1 AI tell)
|
|
329
|
-
|
|
330
|
-
Slice `rows` with `Array.slice(start, end)` based on scroll position is the classic
|
|
331
|
-
re-invention. Use `useVirtualizer` — it handles overscan, dynamic heights, scroll-to-index,
|
|
332
|
-
all of which the hand-rolled version skips.
|
|
333
|
-
|
|
334
|
-
## See Also
|
|
335
|
-
|
|
336
|
-
- `tanstack-table/vue/production-readiness` — keep the virtualizer in a leaf component
|
|
337
|
-
- `tanstack-table/vue/table-state` — narrow reads to avoid parent re-renders
|
|
338
|
-
- `tanstack-table/vue/compose-with-tanstack-query` — infinite-scroll pairs with `useInfiniteQuery`
|
|
339
|
-
- `tanstack-table/table-core/row-expanding` — virtualized + expanding interactions
|
|
340
|
-
- `tanstack-table/table-core/column-layout` — column sizing/pinning + virtualization
|