@tanstack/react-table 9.0.0-alpha.9 → 9.0.0-beta.2

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 (82) hide show
  1. package/README.md +127 -0
  2. package/dist/FlexRender.cjs +61 -0
  3. package/dist/FlexRender.cjs.map +1 -0
  4. package/dist/FlexRender.d.cts +51 -0
  5. package/dist/FlexRender.d.ts +51 -0
  6. package/dist/FlexRender.js +58 -0
  7. package/dist/FlexRender.js.map +1 -0
  8. package/dist/Subscribe.cjs +13 -0
  9. package/dist/Subscribe.cjs.map +1 -0
  10. package/dist/Subscribe.d.cts +101 -0
  11. package/dist/Subscribe.d.ts +101 -0
  12. package/dist/Subscribe.js +13 -0
  13. package/dist/Subscribe.js.map +1 -0
  14. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  15. package/dist/createTableHook.cjs +313 -0
  16. package/dist/createTableHook.cjs.map +1 -0
  17. package/dist/createTableHook.d.cts +358 -0
  18. package/dist/createTableHook.d.ts +358 -0
  19. package/dist/createTableHook.js +311 -0
  20. package/dist/createTableHook.js.map +1 -0
  21. package/dist/flex-render.cjs +5 -0
  22. package/dist/flex-render.d.cts +2 -0
  23. package/dist/flex-render.d.ts +2 -0
  24. package/dist/flex-render.js +3 -0
  25. package/dist/index.cjs +18 -0
  26. package/dist/index.d.cts +6 -0
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +8 -0
  29. package/dist/legacy.cjs +14 -0
  30. package/dist/legacy.d.cts +2 -0
  31. package/dist/legacy.d.ts +2 -0
  32. package/dist/legacy.js +3 -0
  33. package/dist/reactivity.cjs +34 -0
  34. package/dist/reactivity.cjs.map +1 -0
  35. package/dist/reactivity.js +34 -0
  36. package/dist/reactivity.js.map +1 -0
  37. package/dist/static-functions.cjs +9 -0
  38. package/dist/static-functions.d.cts +1 -0
  39. package/dist/static-functions.d.ts +1 -0
  40. package/dist/static-functions.js +3 -0
  41. package/dist/useLegacyTable.cjs +191 -0
  42. package/dist/useLegacyTable.cjs.map +1 -0
  43. package/dist/useLegacyTable.d.cts +233 -0
  44. package/dist/useLegacyTable.d.ts +233 -0
  45. package/dist/useLegacyTable.js +181 -0
  46. package/dist/useLegacyTable.js.map +1 -0
  47. package/dist/useTable.cjs +72 -0
  48. package/dist/useTable.cjs.map +1 -0
  49. package/dist/useTable.d.cts +122 -0
  50. package/dist/useTable.d.ts +122 -0
  51. package/dist/useTable.js +72 -0
  52. package/dist/useTable.js.map +1 -0
  53. package/package.json +41 -22
  54. package/skills/react/client-to-server/SKILL.md +377 -0
  55. package/skills/react/compose-with-tanstack-form/SKILL.md +363 -0
  56. package/skills/react/compose-with-tanstack-pacer/SKILL.md +287 -0
  57. package/skills/react/compose-with-tanstack-query/SKILL.md +467 -0
  58. package/skills/react/compose-with-tanstack-store/SKILL.md +347 -0
  59. package/skills/react/compose-with-tanstack-virtual/SKILL.md +388 -0
  60. package/skills/react/compose-with-tanstack-virtual/references/column-virtualization-and-infinite-scroll.md +136 -0
  61. package/skills/react/getting-started/SKILL.md +388 -0
  62. package/skills/react/migrate-v8-to-v9/SKILL.md +488 -0
  63. package/skills/react/production-readiness/SKILL.md +341 -0
  64. package/skills/react/react-subscribe-compiler-compat/SKILL.md +269 -0
  65. package/skills/react/table-state/SKILL.md +432 -0
  66. package/src/FlexRender.tsx +136 -0
  67. package/src/Subscribe.ts +153 -0
  68. package/src/createTableHook.tsx +1121 -0
  69. package/src/flex-render.ts +1 -0
  70. package/src/index.ts +6 -0
  71. package/src/legacy.ts +3 -0
  72. package/src/reactivity.ts +41 -0
  73. package/src/static-functions.ts +1 -0
  74. package/src/useLegacyTable.ts +487 -0
  75. package/src/useTable.ts +191 -0
  76. package/dist/cjs/index.cjs +0 -77
  77. package/dist/cjs/index.cjs.map +0 -1
  78. package/dist/cjs/index.d.cts +0 -9
  79. package/dist/esm/index.d.ts +0 -9
  80. package/dist/esm/index.js +0 -55
  81. package/dist/esm/index.js.map +0 -1
  82. package/src/index.tsx +0 -92
@@ -0,0 +1,388 @@
1
+ ---
2
+ name: react/compose-with-tanstack-virtual
3
+ description: >
4
+ `@tanstack/react-table` v9 does NOT include virtualization — pair with
5
+ `@tanstack/react-virtual`. Standard row-virtualization pattern: get the row
6
+ array from `table.getRowModel().rows`, feed `rows.length` to
7
+ `useVirtualizer({ count, estimateSize, getScrollElement, ... })` in the
8
+ DEEPEST possible component (a `TableBody`, NOT `App`), iterate
9
+ `rowVirtualizer.getVirtualItems()` instead of `rows.map`, absolute-position
10
+ each row with `transform: translateY(virtualRow.start)px`, and render
11
+ `<tbody>` as a CSS grid with a fixed total height. Column virtualization
12
+ uses `horizontal: true` plus padding-left/right placeholder cells. An
13
+ experimental ref-mutation variant skips React reconciliation for ~10%
14
+ extra perf but the standard pattern is the default.
15
+ type: composition
16
+ library: tanstack-table
17
+ framework: react
18
+ library_version: '9.0.0-alpha.48'
19
+ requires:
20
+ - react/table-state
21
+ - row-expanding
22
+ sources:
23
+ - TanStack/table:docs/guide/virtualization.md
24
+ - TanStack/table:examples/react/virtualized-rows/src/main.tsx
25
+ - TanStack/table:examples/react/virtualized-columns/src/main.tsx
26
+ - TanStack/table:examples/react/virtualized-infinite-scrolling/src/main.tsx
27
+ - TanStack/table:examples/react/virtualized-rows-experimental/src/main.tsx
28
+ ---
29
+
30
+ This skill builds on `tanstack-table/state-management` and `tanstack-table/react/table-state`. Read those first — the table's row model is what feeds the virtualizer.
31
+
32
+ ## Why this skill exists
33
+
34
+ TanStack Table renders every row in its `getRowModel().rows` array. For 50 rows that's fine; for 50k or 500k it crashes the browser. `@tanstack/react-virtual` only renders the rows that fit inside the scroll container, recycling DOM nodes as the user scrolls.
35
+
36
+ ## Setup
37
+
38
+ ```bash
39
+ pnpm add @tanstack/react-table @tanstack/react-virtual
40
+ ```
41
+
42
+ The two pieces:
43
+
44
+ ```tsx
45
+ import { useTable } from '@tanstack/react-table'
46
+ import { useVirtualizer } from '@tanstack/react-virtual'
47
+ ```
48
+
49
+ ## Core Pattern — row virtualization (standard)
50
+
51
+ The single most important rule: **keep `useVirtualizer` in the deepest component possible.** Any state change in the component that owns the virtualizer re-runs it, blowing away scroll position and measurement cache.
52
+
53
+ ```tsx
54
+ import * as React from 'react'
55
+ import {
56
+ useTable,
57
+ tableFeatures,
58
+ columnSizingFeature,
59
+ rowSortingFeature,
60
+ createSortedRowModel,
61
+ sortFns,
62
+ createColumnHelper,
63
+ } from '@tanstack/react-table'
64
+ import { useVirtualizer } from '@tanstack/react-virtual'
65
+ import type { ReactTable, Row } from '@tanstack/react-table'
66
+ import type { VirtualItem, Virtualizer } from '@tanstack/react-virtual'
67
+
68
+ const features = { columnSizingFeature, rowSortingFeature }
69
+ const columnHelper = createColumnHelper<typeof features, Person>()
70
+
71
+ const columns = columnHelper.columns([
72
+ columnHelper.accessor('id', { header: 'ID', size: 60 }),
73
+ columnHelper.accessor('firstName', { cell: (info) => info.getValue() }),
74
+ columnHelper.accessor('lastName', {
75
+ id: 'lastName',
76
+ cell: (info) => info.getValue(),
77
+ }),
78
+ ])
79
+
80
+ function App() {
81
+ // 1) Scroll container ref + table at App level.
82
+ const tableContainerRef = React.useRef<HTMLDivElement>(null)
83
+ const [data] = React.useState(() => makeData(200_000))
84
+
85
+ const table = useTable({
86
+ features,
87
+ rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
88
+ columns,
89
+ data,
90
+ })
91
+
92
+ return (
93
+ <div
94
+ ref={tableContainerRef}
95
+ style={{ overflow: 'auto', position: 'relative', height: 800 }}
96
+ >
97
+ {/* 2) display: grid — required for absolute positioning + dynamic heights */}
98
+ <table style={{ display: 'grid' }}>
99
+ <thead
100
+ style={{
101
+ display: 'grid',
102
+ position: 'sticky',
103
+ top: 0,
104
+ zIndex: 1,
105
+ height: 34,
106
+ }}
107
+ >
108
+ {table.getHeaderGroups().map((hg) => (
109
+ <tr
110
+ key={hg.id}
111
+ style={{ display: 'flex', height: 34, width: '100%' }}
112
+ >
113
+ {hg.headers.map((h) => (
114
+ <th
115
+ key={h.id}
116
+ style={{
117
+ display: 'flex',
118
+ alignItems: 'center',
119
+ width: h.getSize(),
120
+ }}
121
+ >
122
+ <div onClick={h.column.getToggleSortingHandler()}>
123
+ <table.FlexRender header={h} />
124
+ </div>
125
+ </th>
126
+ ))}
127
+ </tr>
128
+ ))}
129
+ </thead>
130
+ {/* 3) Virtualizer lives inside TableBody, NOT here. */}
131
+ <TableBody table={table} tableContainerRef={tableContainerRef} />
132
+ </table>
133
+ </div>
134
+ )
135
+ }
136
+
137
+ interface TableBodyProps {
138
+ table: ReactTable<typeof features, Person>
139
+ tableContainerRef: React.RefObject<HTMLDivElement | null>
140
+ }
141
+
142
+ function TableBody({ table, tableContainerRef }: TableBodyProps) {
143
+ const { rows } = table.getRowModel()
144
+
145
+ // 4) useVirtualizer in the deepest body component.
146
+ const rowVirtualizer = useVirtualizer<HTMLDivElement, HTMLTableRowElement>({
147
+ count: rows.length,
148
+ estimateSize: () => 33,
149
+ getScrollElement: () => tableContainerRef.current,
150
+ // 5) Skip dynamic measurement on Firefox — it measures border height wrong.
151
+ measureElement:
152
+ typeof window !== 'undefined' &&
153
+ navigator.userAgent.indexOf('Firefox') === -1
154
+ ? (el) => el.getBoundingClientRect().height
155
+ : undefined,
156
+ overscan: 5,
157
+ })
158
+
159
+ return (
160
+ <tbody
161
+ style={{
162
+ display: 'grid',
163
+ height: `${rowVirtualizer.getTotalSize()}px`, // total scrollable height
164
+ position: 'relative', // for absolute child rows
165
+ }}
166
+ >
167
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => {
168
+ const row = rows[virtualRow.index]
169
+ return (
170
+ <tr
171
+ key={row.id}
172
+ data-index={virtualRow.index}
173
+ ref={(node) => rowVirtualizer.measureElement(node)}
174
+ style={{
175
+ display: 'flex',
176
+ position: 'absolute',
177
+ transform: `translateY(${virtualRow.start}px)`,
178
+ width: '100%',
179
+ }}
180
+ >
181
+ {row.getAllCells().map((cell) => (
182
+ <td
183
+ key={cell.id}
184
+ style={{ display: 'flex', width: cell.column.getSize() }}
185
+ >
186
+ <table.FlexRender cell={cell} />
187
+ </td>
188
+ ))}
189
+ </tr>
190
+ )
191
+ })}
192
+ </tbody>
193
+ )
194
+ }
195
+ ```
196
+
197
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
198
+
199
+ ## Column virtualization and infinite scroll
200
+
201
+ Column virtualization (`horizontal: true` + placeholder padding cells) and infinite scroll via `useInfiniteQuery` + `manualSorting: true` — see [column-virtualization-and-infinite-scroll.md](references/column-virtualization-and-infinite-scroll.md). That file also covers the HIGH-priority `manualSorting` failure mode and the column-virt padding-cells failure mode.
202
+
203
+ ## Experimental ref-mutation variant
204
+
205
+ `examples/react/virtualized-rows-experimental/` and `virtualized-columns-experimental/` mutate row `style` directly via the virtualizer's `onChange` callback, skipping React reconciliation on scroll. Roughly **10% rendering perf gain** in maintainer benchmarks. The pattern is valid but the standard pattern above is the documented default; reach for the experimental version only when measured perf demands it.
206
+
207
+ ## Common Mistakes
208
+
209
+ ### CRITICAL `useVirtualizer` in the same component as `useTable`
210
+
211
+ Wrong:
212
+
213
+ ```tsx
214
+ function App() {
215
+ const rowVirtualizer = useVirtualizer({
216
+ /* … */
217
+ }) // virtualizer too high
218
+ const table = useTable(opts)
219
+ return <TableBody table={table} virtualizer={rowVirtualizer} />
220
+ }
221
+ ```
222
+
223
+ Correct:
224
+
225
+ ```tsx
226
+ function App() {
227
+ const tableContainerRef = React.useRef<HTMLDivElement>(null)
228
+ const table = useTable(opts)
229
+ return (
230
+ <div ref={tableContainerRef}>
231
+ <TableBody table={table} tableContainerRef={tableContainerRef} />
232
+ </div>
233
+ )
234
+ }
235
+ function TableBody({ table, tableContainerRef }) {
236
+ const rowVirtualizer = useVirtualizer({
237
+ /* … */
238
+ }) // virtualizer deepest
239
+ /* … */
240
+ }
241
+ ```
242
+
243
+ Any state change in the component owning the virtualizer re-runs it — losing scroll position and remeasuring every row.
244
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
245
+
246
+ ### CRITICAL Rendering `rows.map` directly on a large dataset
247
+
248
+ Wrong:
249
+
250
+ ```tsx
251
+ <tbody>
252
+ {rows.map((row) => (
253
+ <tr key={row.id}>...</tr>
254
+ ))}
255
+ </tbody>
256
+ // 200k DOM rows — browser crashes.
257
+ ```
258
+
259
+ Correct:
260
+
261
+ ```tsx
262
+ <tbody
263
+ style={{
264
+ display: 'grid',
265
+ height: `${rowVirtualizer.getTotalSize()}px`,
266
+ position: 'relative',
267
+ }}
268
+ >
269
+ {rowVirtualizer.getVirtualItems().map((vr) => {
270
+ const row = rows[vr.index]
271
+ return (
272
+ <tr
273
+ style={{
274
+ position: 'absolute',
275
+ transform: `translateY(${vr.start}px)` /* … */,
276
+ }}
277
+ >
278
+ {/* … */}
279
+ </tr>
280
+ )
281
+ })}
282
+ </tbody>
283
+ ```
284
+
285
+ Use `getVirtualItems()` so only the visible window renders.
286
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
287
+
288
+ ### CRITICAL Missing `display: grid` + absolute positioning
289
+
290
+ Wrong:
291
+
292
+ ```tsx
293
+ <tbody>
294
+ {rowVirtualizer.getVirtualItems().map((vr) => (
295
+ <tr key={vr.key}>{/* no transform, no absolute */}</tr>
296
+ ))}
297
+ </tbody>
298
+ ```
299
+
300
+ Correct:
301
+
302
+ ```tsx
303
+ <tbody
304
+ style={{
305
+ display: 'grid',
306
+ height: `${rowVirtualizer.getTotalSize()}px`,
307
+ position: 'relative',
308
+ }}
309
+ >
310
+ {rowVirtualizer.getVirtualItems().map((vr) => (
311
+ <tr
312
+ style={{
313
+ display: 'flex',
314
+ position: 'absolute',
315
+ transform: `translateY(${vr.start}px)`,
316
+ width: '100%',
317
+ }}
318
+ >
319
+ {/* … */}
320
+ </tr>
321
+ ))}
322
+ </tbody>
323
+ ```
324
+
325
+ The semantic `<table>` layout collides with absolute positioning. CSS grid lets the rows position themselves freely while keeping semantic tags. Without `transform: translateY(start)px` all rows render at `top: 0`.
326
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
327
+
328
+ ### HIGH Using `measureElement` on Firefox
329
+
330
+ Wrong:
331
+
332
+ ```tsx
333
+ const rowVirtualizer = useVirtualizer({
334
+ count: rows.length,
335
+ estimateSize: () => 33,
336
+ getScrollElement: () => ref.current,
337
+ measureElement: (el) => el.getBoundingClientRect().height, // jitters in Firefox
338
+ })
339
+ ```
340
+
341
+ Correct:
342
+
343
+ ```tsx
344
+ const rowVirtualizer = useVirtualizer({
345
+ count: rows.length,
346
+ estimateSize: () => 33,
347
+ getScrollElement: () => ref.current,
348
+ measureElement:
349
+ typeof window !== 'undefined' &&
350
+ navigator.userAgent.indexOf('Firefox') === -1
351
+ ? (el) => el.getBoundingClientRect().height
352
+ : undefined,
353
+ })
354
+ ```
355
+
356
+ Firefox returns inconsistent row heights for table rows, causing flicker. Guard the option.
357
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
358
+
359
+ ### HIGH Storing the ref instead of using the callback-ref form
360
+
361
+ Wrong:
362
+
363
+ ```tsx
364
+ const rowRef = React.useRef(null)
365
+ <tr ref={rowRef} /*…*/ />
366
+ // rowVirtualizer can't remeasure when row content changes height
367
+ ```
368
+
369
+ Correct:
370
+
371
+ ```tsx
372
+ <tr ref={(node) => rowVirtualizer.measureElement(node)} /*…*/ />
373
+ ```
374
+
375
+ The pattern is a ref callback that calls `measureElement(node)` — passing a stored ref means the virtualizer never gets a chance to remeasure.
376
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
377
+
378
+ For HIGH-priority failure modes specific to column virtualization (missing padding placeholders) and infinite scroll (`manualSorting` requirement), see [column-virtualization-and-infinite-scroll.md](references/column-virtualization-and-infinite-scroll.md).
379
+
380
+ ## See Also
381
+
382
+ - `tanstack-table/react/production-readiness` — keep virtualizers in deepest components.
383
+ - `tanstack-table/react/compose-with-tanstack-query` — `useInfiniteQuery` integration.
384
+ - `tanstack-table/react/table-state` — the row model API the virtualizer reads from.
385
+
386
+ ## References
387
+
388
+ - [column-virtualization-and-infinite-scroll.md](references/column-virtualization-and-infinite-scroll.md) — `horizontal: true` column virtualization with placeholder padding cells, `useInfiniteQuery` + `manualSorting: true` integration, plus HIGH-priority failure modes for both
@@ -0,0 +1,136 @@
1
+ # Column virtualization and infinite scroll — React + TanStack Virtual
2
+
3
+ Extended composition patterns extracted from `SKILL.md`. The primary row-virtualization pattern and the experimental ref-mutation variant remain inline in the SKILL; this file covers column virtualization and the `useInfiniteQuery` integration.
4
+
5
+ ## Column virtualization
6
+
7
+ Same shape as row virtualization with `horizontal: true` and left/right placeholder cells so unrendered columns still take up scroll width:
8
+
9
+ ```tsx
10
+ const columnVirtualizer = useVirtualizer({
11
+ count: table.getVisibleLeafColumns().length,
12
+ estimateSize: (i) => table.getVisibleLeafColumns()[i].getSize(),
13
+ getScrollElement: () => tableContainerRef.current,
14
+ horizontal: true,
15
+ overscan: 3,
16
+ })
17
+
18
+ const virtualColumns = columnVirtualizer.getVirtualItems()
19
+ const virtualPaddingLeft = virtualColumns[0]?.start ?? 0
20
+ const virtualPaddingRight =
21
+ columnVirtualizer.getTotalSize() -
22
+ (virtualColumns[virtualColumns.length - 1]?.end ?? 0)
23
+
24
+ // In each row:
25
+ <tr>
26
+ {virtualPaddingLeft > 0 ? <td style={{ width: virtualPaddingLeft }} /> : null}
27
+ {virtualColumns.map((vc) => {
28
+ const cell = row.getVisibleCells()[vc.index]
29
+ return <td key={cell.id}><table.FlexRender cell={cell} /></td>
30
+ })}
31
+ {virtualPaddingRight > 0 ? <td style={{ width: virtualPaddingRight }} /> : null}
32
+ </tr>
33
+ ```
34
+
35
+ Source: `examples/react/virtualized-columns/src/main.tsx`.
36
+
37
+ ## Infinite scroll — Virtual + `useInfiniteQuery`
38
+
39
+ ```tsx
40
+ const dataQuery = useInfiniteQuery({
41
+ queryKey: ['people', sorting],
42
+ queryFn: ({ pageParam = 0 }) => fetchPage(pageParam, sorting),
43
+ getNextPageParam: (lastPage, allPages) => allPages.length,
44
+ placeholderData: keepPreviousData,
45
+ })
46
+
47
+ const flatRows = React.useMemo(
48
+ () => dataQuery.data?.pages.flatMap((p) => p.rows) ?? [],
49
+ [dataQuery.data],
50
+ )
51
+
52
+ const table = useTable({
53
+ features: tableFeatures({ rowSortingFeature }),
54
+ rowModels: {}, // server sorts each page
55
+ columns,
56
+ data: flatRows,
57
+ manualSorting: true,
58
+ // ...
59
+ })
60
+
61
+ // Inside TableBody, scroll handler:
62
+ React.useEffect(() => {
63
+ const el = tableContainerRef.current
64
+ if (!el) return
65
+ const onScroll = () => {
66
+ if (
67
+ el.scrollHeight - el.scrollTop - el.clientHeight < 500 &&
68
+ !dataQuery.isFetching
69
+ ) {
70
+ dataQuery.fetchNextPage()
71
+ }
72
+ }
73
+ el.addEventListener('scroll', onScroll)
74
+ return () => el.removeEventListener('scroll', onScroll)
75
+ }, [dataQuery])
76
+ ```
77
+
78
+ Source: `examples/react/virtualized-infinite-scrolling/src/main.tsx`.
79
+
80
+ ## Common Mistakes (column virt + infinite scroll)
81
+
82
+ ### HIGH For column virtualization: missing padding placeholder cells
83
+
84
+ Wrong:
85
+
86
+ ```tsx
87
+ <tr>
88
+ {virtualColumns.map((vc) => (
89
+ <td key={vc.index}>...</td>
90
+ ))}
91
+ </tr>
92
+ // Unrendered columns aren't taking up scroll space → visible columns slide left.
93
+ ```
94
+
95
+ Correct:
96
+
97
+ ```tsx
98
+ <tr>
99
+ {virtualPaddingLeft > 0 ? <td style={{ width: virtualPaddingLeft }} /> : null}
100
+ {virtualColumns.map((vc) => (
101
+ <td key={vc.index}>...</td>
102
+ ))}
103
+ {virtualPaddingRight > 0 ? (
104
+ <td style={{ width: virtualPaddingRight }} />
105
+ ) : null}
106
+ </tr>
107
+ ```
108
+
109
+ Source: `examples/react/virtualized-columns/src/main.tsx`.
110
+
111
+ ### HIGH Infinite scroll without `manualSorting`
112
+
113
+ Wrong:
114
+
115
+ ```tsx
116
+ const table = useTable({
117
+ features: tableFeatures({ rowSortingFeature }),
118
+ rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
119
+ data: flatRows,
120
+ })
121
+ // Each new page arrives → table re-sorts everything → row order scrambles between pages.
122
+ ```
123
+
124
+ Correct:
125
+
126
+ ```tsx
127
+ const table = useTable({
128
+ features: tableFeatures({ rowSortingFeature }),
129
+ rowModels: {}, // server sorts each page
130
+ data: flatRows,
131
+ manualSorting: true,
132
+ })
133
+ ```
134
+
135
+ With `useInfiniteQuery`, you must fire a fresh query on sort changes (key your `queryKey` on `sorting`) and set `manualSorting: true` so the table doesn't re-sort accumulated pages.
136
+ Source: `examples/react/virtualized-infinite-scrolling/src/main.tsx`.