@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,467 @@
1
+ ---
2
+ name: react/compose-with-tanstack-query
3
+ description: >
4
+ Server-side / async data flow for `@tanstack/react-table` v9 with
5
+ `@tanstack/react-query`. Canonical pattern: external pagination atom via
6
+ `useCreateAtom<PaginationState>` + `options.atoms` (NOT `state + on*Change`),
7
+ pagination object as part of `queryKey`, `manualPagination: true`,
8
+ `placeholderData: keepPreviousData` to avoid the 0-rows flash, and
9
+ `defaultData = useMemo(() => [], [])` to keep `data` reference stable
10
+ between fetches. `rowCount` from the API response so `getPageCount()` works.
11
+ type: composition
12
+ library: tanstack-table
13
+ framework: react
14
+ library_version: '9.0.0-alpha.48'
15
+ requires:
16
+ - react/client-to-server
17
+ - pagination
18
+ - react/table-state
19
+ sources:
20
+ - TanStack/table:examples/react/with-tanstack-query/src/main.tsx
21
+ - TanStack/table:examples/react/with-tanstack-query/src/fetchData.ts
22
+ ---
23
+
24
+ This skill builds on `tanstack-table/state-management`, `tanstack-table/react/table-state`, and `tanstack-table/react/client-to-server`. Read those first — Query composition is `client-to-server` with a specific server.
25
+
26
+ ## Why this pattern
27
+
28
+ A v9 React table written against TanStack Query has three load-bearing decisions:
29
+
30
+ 1. **External pagination atom**, not `state` + `onPaginationChange`. Cleaner because the table writes to the atom directly; the query's `queryKey` watches the atom; refetches happen automatically.
31
+ 2. **`placeholderData: keepPreviousData`** so the previous page stays visible while the next page fetches. Without it the table collapses to 0 rows on every page change and the scroll position jumps.
32
+ 3. **Stable `data` fallback** (`defaultData = useMemo(() => [], [])`). `data: dataQuery.data?.rows ?? []` in JSX produces a new array each render and busts internal memos.
33
+
34
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
35
+
36
+ ## Setup
37
+
38
+ ```bash
39
+ pnpm add @tanstack/react-table @tanstack/react-query @tanstack/react-store
40
+ ```
41
+
42
+ Mount one `<QueryClientProvider>` at the root:
43
+
44
+ ```tsx
45
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
46
+
47
+ const queryClient = new QueryClient()
48
+
49
+ ReactDOM.createRoot(rootElement).render(
50
+ <React.StrictMode>
51
+ <QueryClientProvider client={queryClient}>
52
+ <App />
53
+ </QueryClientProvider>
54
+ </React.StrictMode>,
55
+ )
56
+ ```
57
+
58
+ ## Core Pattern — canonical server-paginated table
59
+
60
+ ```tsx
61
+ import * as React from 'react'
62
+ import { keepPreviousData, useQuery } from '@tanstack/react-query'
63
+ import { useCreateAtom, useSelector } from '@tanstack/react-store'
64
+ import {
65
+ useTable,
66
+ tableFeatures,
67
+ rowPaginationFeature,
68
+ createColumnHelper,
69
+ } from '@tanstack/react-table'
70
+ import type { PaginationState } from '@tanstack/react-table'
71
+ import { fetchData } from './fetchData' // returns { rows, rowCount }
72
+ import type { Person } from './fetchData'
73
+
74
+ const features = tableFeatures({ rowPaginationFeature })
75
+
76
+ const columnHelper = createColumnHelper<typeof features, Person>()
77
+ const columns = columnHelper.columns([
78
+ columnHelper.accessor('firstName', {
79
+ header: 'First Name',
80
+ cell: (i) => i.getValue(),
81
+ }),
82
+ columnHelper.accessor('lastName', {
83
+ header: 'Last Name',
84
+ cell: (i) => i.getValue(),
85
+ }),
86
+ columnHelper.accessor('age', { header: 'Age' }),
87
+ columnHelper.accessor('visits', { header: 'Visits' }),
88
+ columnHelper.accessor('status', { header: 'Status' }),
89
+ columnHelper.accessor('progress', { header: 'Profile Progress' }),
90
+ ])
91
+
92
+ function App() {
93
+ // 1) Pagination atom — stable identity via useCreateAtom.
94
+ const paginationAtom = useCreateAtom<PaginationState>({
95
+ pageIndex: 0,
96
+ pageSize: 10,
97
+ })
98
+ // 2) Subscribe so the query refetches on pagination changes.
99
+ const pagination = useSelector(paginationAtom, (s) => s)
100
+
101
+ // 3) Query keyed on the pagination object — refetch on every page/size change.
102
+ const dataQuery = useQuery({
103
+ queryKey: ['data', pagination],
104
+ queryFn: () => fetchData(pagination),
105
+ placeholderData: keepPreviousData, // 4) avoid 0-rows flash
106
+ })
107
+
108
+ // 5) Stable fallback — fresh `[]` in JSX would bust internal memos.
109
+ const defaultData = React.useMemo(() => [], [])
110
+
111
+ // 6) Manual pagination + rowCount; no paginatedRowModel.
112
+ const table = useTable(
113
+ {
114
+ features,
115
+ rowModels: {},
116
+ columns,
117
+ data: dataQuery.data?.rows ?? defaultData,
118
+ rowCount: dataQuery.data?.rowCount,
119
+ atoms: { pagination: paginationAtom }, // table writes here directly
120
+ manualPagination: true,
121
+ },
122
+ (state) => state,
123
+ )
124
+
125
+ return (
126
+ <>
127
+ <table>
128
+ <thead>{/* table.FlexRender header={h} */}</thead>
129
+ <tbody>{/* table.FlexRender cell={c} */}</tbody>
130
+ </table>
131
+ <div className="controls">
132
+ <button
133
+ onClick={() => table.firstPage()}
134
+ disabled={!table.getCanPreviousPage()}
135
+ >
136
+ {'<<'}
137
+ </button>
138
+ <button
139
+ onClick={() => table.previousPage()}
140
+ disabled={!table.getCanPreviousPage()}
141
+ >
142
+ {'<'}
143
+ </button>
144
+ <button
145
+ onClick={() => table.nextPage()}
146
+ disabled={!table.getCanNextPage()}
147
+ >
148
+ {'>'}
149
+ </button>
150
+ <button
151
+ onClick={() => table.lastPage()}
152
+ disabled={!table.getCanNextPage()}
153
+ >
154
+ {'>>'}
155
+ </button>
156
+ <span>
157
+ Page{' '}
158
+ <strong>
159
+ {pagination.pageIndex + 1} of {table.getPageCount()}
160
+ </strong>
161
+ </span>
162
+ <select
163
+ value={pagination.pageSize}
164
+ onChange={(e) => table.setPageSize(Number(e.target.value))}
165
+ >
166
+ {[10, 20, 30, 40, 50].map((s) => (
167
+ <option key={s} value={s}>
168
+ Show {s}
169
+ </option>
170
+ ))}
171
+ </select>
172
+ {dataQuery.isFetching ? 'Loading...' : null}
173
+ </div>
174
+ </>
175
+ )
176
+ }
177
+ ```
178
+
179
+ Source: `examples/react/with-tanstack-query/src/main.tsx` (this is the canonical example, near-verbatim).
180
+
181
+ ## Adding sort + filter
182
+
183
+ The same pattern extends to multiple slices. Key the query on each, set the matching `manual*` flag, drop the matching `rowModels` factory.
184
+
185
+ ```tsx
186
+ const paginationAtom = useCreateAtom<PaginationState>({
187
+ pageIndex: 0,
188
+ pageSize: 10,
189
+ })
190
+ const sortingAtom = useCreateAtom<SortingState>([])
191
+ const columnFiltersAtom = useCreateAtom<ColumnFiltersState>([])
192
+
193
+ const pagination = useSelector(paginationAtom)
194
+ const sorting = useSelector(sortingAtom)
195
+ const columnFilters = useSelector(columnFiltersAtom)
196
+
197
+ const dataQuery = useQuery({
198
+ queryKey: ['data', { pagination, sorting, columnFilters }],
199
+ queryFn: () => fetchData({ pagination, sorting, columnFilters }),
200
+ placeholderData: keepPreviousData,
201
+ })
202
+
203
+ const table = useTable({
204
+ features: tableFeatures({
205
+ rowPaginationFeature,
206
+ rowSortingFeature,
207
+ columnFilteringFeature,
208
+ }),
209
+ rowModels: {}, // server owns sort/filter/paginate
210
+ columns,
211
+ data: dataQuery.data?.rows ?? defaultData,
212
+ rowCount: dataQuery.data?.rowCount,
213
+ atoms: {
214
+ pagination: paginationAtom,
215
+ sorting: sortingAtom,
216
+ columnFilters: columnFiltersAtom,
217
+ },
218
+ manualSorting: true,
219
+ manualFiltering: true,
220
+ manualPagination: true,
221
+ })
222
+ ```
223
+
224
+ ## Mutations and invalidation
225
+
226
+ TanStack Table is a downstream consumer — it has no way to know the server data changed. Call `queryClient.invalidateQueries` after mutations:
227
+
228
+ ```tsx
229
+ const queryClient = useQueryClient()
230
+ const addPerson = useMutation({
231
+ mutationFn: createPerson,
232
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['data'] }),
233
+ })
234
+ ```
235
+
236
+ ## Common Mistakes
237
+
238
+ ### CRITICAL Forgetting `manualPagination` / `manualSorting` / `manualFiltering`
239
+
240
+ Wrong:
241
+
242
+ ```tsx
243
+ const table = useTable({
244
+ features,
245
+ rowModels: { paginatedRowModel: createPaginatedRowModel() },
246
+ columns,
247
+ data: query.data?.rows ?? [],
248
+ // missing manualPagination
249
+ })
250
+ ```
251
+
252
+ Correct:
253
+
254
+ ```tsx
255
+ const table = useTable({
256
+ features,
257
+ rowModels: {}, // drop paginatedRowModel
258
+ columns,
259
+ data: query.data?.rows ?? defaultData,
260
+ rowCount: query.data?.rowCount,
261
+ atoms: { pagination: paginationAtom },
262
+ manualPagination: true,
263
+ })
264
+ ```
265
+
266
+ Without `manualPagination: true`, the table re-paginates the server-already-paginated 10-row "dataset" — `getPageCount()` returns 1, and the pager locks at "Page 1 of 1".
267
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
268
+
269
+ ### CRITICAL Missing `rowCount`
270
+
271
+ Wrong:
272
+
273
+ ```tsx
274
+ const table = useTable({
275
+ features,
276
+ rowModels: {},
277
+ columns,
278
+ data: query.data?.rows ?? defaultData,
279
+ atoms: { pagination: paginationAtom },
280
+ manualPagination: true,
281
+ // missing rowCount
282
+ })
283
+ ```
284
+
285
+ Correct:
286
+
287
+ ```tsx
288
+ const table = useTable({
289
+ features,
290
+ rowModels: {},
291
+ columns,
292
+ data: query.data?.rows ?? defaultData,
293
+ rowCount: query.data?.rowCount, // ← required for accurate pager
294
+ atoms: { pagination: paginationAtom },
295
+ manualPagination: true,
296
+ })
297
+ ```
298
+
299
+ `getPageCount()` falls back to `Math.ceil(data.length / pageSize)` — which equals 1 when the server returned one page.
300
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
301
+
302
+ ### CRITICAL `queryKey` doesn't include the pagination state
303
+
304
+ Wrong:
305
+
306
+ ```tsx
307
+ useQuery({
308
+ queryKey: ['data'], // never changes
309
+ queryFn: () => fetchData(pagination),
310
+ })
311
+ ```
312
+
313
+ Correct:
314
+
315
+ ```tsx
316
+ useQuery({
317
+ queryKey: ['data', pagination], // refetch on pagination change
318
+ queryFn: () => fetchData(pagination),
319
+ placeholderData: keepPreviousData,
320
+ })
321
+ ```
322
+
323
+ Query has no way to know its inputs changed unless they're in `queryKey`. Pager button clicks update the atom but the query never refetches.
324
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
325
+
326
+ ### HIGH Skipping `placeholderData: keepPreviousData`
327
+
328
+ Wrong:
329
+
330
+ ```tsx
331
+ useQuery({
332
+ queryKey: ['data', pagination],
333
+ queryFn: () => fetchData(pagination),
334
+ })
335
+ // Between pages: table renders 0 rows, container collapses, scroll position jumps.
336
+ ```
337
+
338
+ Correct:
339
+
340
+ ```tsx
341
+ useQuery({
342
+ queryKey: ['data', pagination],
343
+ queryFn: () => fetchData(pagination),
344
+ placeholderData: keepPreviousData, // previous page stays visible while fetching
345
+ })
346
+ ```
347
+
348
+ The previous page renders during the fetch — no flash, no jump.
349
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
350
+
351
+ ### HIGH Recreating `data: query.data?.rows ?? []` in JSX
352
+
353
+ Wrong:
354
+
355
+ ```tsx
356
+ const table = useTable({
357
+ features,
358
+ rowModels: {},
359
+ columns,
360
+ data: query.data?.rows ?? [], // new identity every render
361
+ // ...
362
+ })
363
+ ```
364
+
365
+ Correct:
366
+
367
+ ```tsx
368
+ const defaultData = React.useMemo(() => [], [])
369
+ // or: const EMPTY: Person[] = [] at module scope
370
+
371
+ const table = useTable({
372
+ features,
373
+ rowModels: {},
374
+ columns,
375
+ data: query.data?.rows ?? defaultData,
376
+ // ...
377
+ })
378
+ ```
379
+
380
+ `?? []` creates a fresh array reference each render, busting internal memos that depend on `data` identity.
381
+ Source: `examples/react/with-tanstack-query/src/main.tsx` (uses `useMemo`).
382
+
383
+ ### HIGH Mixing `state.pagination` + `onPaginationChange` AND `atoms.pagination`
384
+
385
+ Wrong:
386
+
387
+ ```tsx
388
+ useTable({
389
+ features,
390
+ rowModels: {},
391
+ columns,
392
+ data,
393
+ state: { pagination }, // silently ignored
394
+ onPaginationChange: setPagination, // silently ignored
395
+ atoms: { pagination: paginationAtom }, // wins
396
+ manualPagination: true,
397
+ })
398
+ ```
399
+
400
+ Correct:
401
+
402
+ ```tsx
403
+ // Pick one. The atom pattern is canonical for Query.
404
+ useTable({
405
+ features,
406
+ rowModels: {},
407
+ columns,
408
+ data,
409
+ atoms: { pagination: paginationAtom },
410
+ manualPagination: true,
411
+ })
412
+ ```
413
+
414
+ Precedence is `atoms` > `state` > internal. The `state` plumbing is dead.
415
+ Source: `examples/react/basic-external-atoms/src/main.tsx`.
416
+
417
+ ### HIGH Forgetting `invalidateQueries` after mutations
418
+
419
+ Wrong:
420
+
421
+ ```tsx
422
+ const addPerson = useMutation({
423
+ mutationFn: createPerson,
424
+ // missing onSuccess invalidation
425
+ })
426
+ // Table never sees the new row.
427
+ ```
428
+
429
+ Correct:
430
+
431
+ ```tsx
432
+ const queryClient = useQueryClient()
433
+ const addPerson = useMutation({
434
+ mutationFn: createPerson,
435
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['data'] }),
436
+ })
437
+ ```
438
+
439
+ The table is downstream of Query. Mutations must invalidate the relevant query keys.
440
+ Source: docs/framework/react/react-query.
441
+
442
+ ### MEDIUM Leaving `paginatedRowModel` registered when the server paginates
443
+
444
+ Wrong:
445
+
446
+ ```tsx
447
+ rowModels: {
448
+ paginatedRowModel: createPaginatedRowModel()
449
+ } // ships unused code
450
+ ```
451
+
452
+ Correct:
453
+
454
+ ```tsx
455
+ rowModels: {
456
+ } // server paginates; drop the factory
457
+ ```
458
+
459
+ Bundle waste plus a foot-gun if `manualPagination` is ever flipped off.
460
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
461
+
462
+ ## See Also
463
+
464
+ - `tanstack-table/react/client-to-server` — the underlying manual-mode mechanics.
465
+ - `tanstack-table/react/compose-with-tanstack-store` — owning state slices via atoms.
466
+ - `tanstack-table/react/compose-with-tanstack-virtual` — infinite scroll = Virtual + `useInfiniteQuery`.
467
+ - `tanstack-table/react/compose-with-tanstack-pacer` — debounce filter writes that feed the query.