@tanstack/react-table 9.0.0-alpha.47 → 9.0.0-alpha.48
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 +10 -0
- package/package.json +6 -4
- package/skills/react/client-to-server/SKILL.md +377 -0
- package/skills/react/compose-with-tanstack-form/SKILL.md +363 -0
- package/skills/react/compose-with-tanstack-pacer/SKILL.md +287 -0
- package/skills/react/compose-with-tanstack-query/SKILL.md +467 -0
- package/skills/react/compose-with-tanstack-store/SKILL.md +347 -0
- package/skills/react/compose-with-tanstack-virtual/SKILL.md +388 -0
- package/skills/react/compose-with-tanstack-virtual/references/column-virtualization-and-infinite-scroll.md +136 -0
- package/skills/react/getting-started/SKILL.md +388 -0
- package/skills/react/migrate-v8-to-v9/SKILL.md +488 -0
- package/skills/react/production-readiness/SKILL.md +341 -0
- package/skills/react/react-subscribe-compiler-compat/SKILL.md +269 -0
- package/skills/react/table-state/SKILL.md +432 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react/table-state
|
|
3
|
+
description: >
|
|
4
|
+
Wiring reactivity for `@tanstack/react-table` v9. Covers `useTable` (and its
|
|
5
|
+
second-argument selector), reading state via `table.state` / `table.store` /
|
|
6
|
+
`table.atoms.<slice>`, rendering with `table.FlexRender`, opting subtrees into
|
|
7
|
+
fine-grained reactivity with `<table.Subscribe>` and the standalone
|
|
8
|
+
`<Subscribe>`, owning slices with external atoms via `useCreateAtom` +
|
|
9
|
+
`options.atoms`, and packaging shared config into a reusable hook with
|
|
10
|
+
`createTableHook` (`useAppTable`, `createAppColumnHelper`, `table.AppTable` /
|
|
11
|
+
`table.AppHeader` / `table.AppCell` / `table.AppFooter`). Routing keywords:
|
|
12
|
+
useTable, useSelector, useCreateAtom, atoms, react-store, table.Subscribe,
|
|
13
|
+
FlexRender.
|
|
14
|
+
type: framework
|
|
15
|
+
library: tanstack-table
|
|
16
|
+
framework: react
|
|
17
|
+
library_version: '9.0.0-alpha.47'
|
|
18
|
+
requires:
|
|
19
|
+
- state-management
|
|
20
|
+
- setup
|
|
21
|
+
sources:
|
|
22
|
+
- TanStack/table:docs/framework/react/guide/table-state.md
|
|
23
|
+
- TanStack/table:packages/react-table/src/useTable.ts
|
|
24
|
+
- TanStack/table:packages/react-table/src/Subscribe.ts
|
|
25
|
+
- TanStack/table:packages/react-table/src/FlexRender.tsx
|
|
26
|
+
- TanStack/table:packages/react-table/src/createTableHook.tsx
|
|
27
|
+
- TanStack/table:examples/react/basic-use-table/src/main.tsx
|
|
28
|
+
- TanStack/table:examples/react/basic-subscribe/src/main.tsx
|
|
29
|
+
- TanStack/table:examples/react/basic-external-atoms/src/main.tsx
|
|
30
|
+
- TanStack/table:examples/react/basic-external-state/src/main.tsx
|
|
31
|
+
- TanStack/table:examples/react/basic-use-app-table/src/main.tsx
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
This skill builds on `tanstack-table/state-management` and `tanstack-table/setup`. Read those first — `state-management` explains the v9 atom model (per-slice readonly `table.atoms`, internal writable `table.baseAtoms`, flat `table.store`), and this skill shows how each surface is consumed in React.
|
|
35
|
+
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
Every React v9 table follows the same shape. Define `_features`, `_rowModels`, and `columns` at module scope so their references are stable, then call `useTable` and render with `<table.FlexRender>`.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {
|
|
42
|
+
useTable,
|
|
43
|
+
tableFeatures,
|
|
44
|
+
rowSortingFeature,
|
|
45
|
+
createSortedRowModel,
|
|
46
|
+
sortFns,
|
|
47
|
+
createColumnHelper,
|
|
48
|
+
} from '@tanstack/react-table'
|
|
49
|
+
import type { ColumnDef } from '@tanstack/react-table'
|
|
50
|
+
|
|
51
|
+
type Person = { firstName: string; lastName: string; age: number }
|
|
52
|
+
|
|
53
|
+
// Module-scope = stable identity. Critical for re-render perf.
|
|
54
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
55
|
+
const columnHelper = createColumnHelper<typeof _features, Person>()
|
|
56
|
+
|
|
57
|
+
const columns = columnHelper.columns([
|
|
58
|
+
columnHelper.accessor('firstName', { header: 'First' }),
|
|
59
|
+
columnHelper.accessor('lastName', { header: 'Last' }),
|
|
60
|
+
columnHelper.accessor('age', { header: 'Age' }),
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
function PeopleTable({ data }: { data: Person[] }) {
|
|
64
|
+
const table = useTable(
|
|
65
|
+
{
|
|
66
|
+
_features,
|
|
67
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
68
|
+
columns,
|
|
69
|
+
data,
|
|
70
|
+
},
|
|
71
|
+
(state) => state, // default selector — matches v8 ergonomics
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<table>
|
|
76
|
+
<thead>
|
|
77
|
+
{table.getHeaderGroups().map((hg) => (
|
|
78
|
+
<tr key={hg.id}>
|
|
79
|
+
{hg.headers.map((h) => (
|
|
80
|
+
<th key={h.id} onClick={h.column.getToggleSortingHandler()}>
|
|
81
|
+
{h.isPlaceholder ? null : <table.FlexRender header={h} />}
|
|
82
|
+
</th>
|
|
83
|
+
))}
|
|
84
|
+
</tr>
|
|
85
|
+
))}
|
|
86
|
+
</thead>
|
|
87
|
+
<tbody>
|
|
88
|
+
{table.getRowModel().rows.map((row) => (
|
|
89
|
+
<tr key={row.id}>
|
|
90
|
+
{row.getAllCells().map((cell) => (
|
|
91
|
+
<td key={cell.id}>
|
|
92
|
+
<table.FlexRender cell={cell} />
|
|
93
|
+
</td>
|
|
94
|
+
))}
|
|
95
|
+
</tr>
|
|
96
|
+
))}
|
|
97
|
+
</tbody>
|
|
98
|
+
</table>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Source: `examples/react/basic-use-table/src/main.tsx`.
|
|
104
|
+
|
|
105
|
+
## Core Patterns
|
|
106
|
+
|
|
107
|
+
### 1. `useTable` selector (second argument)
|
|
108
|
+
|
|
109
|
+
The default is `(state) => state` — the component re-renders on any state change. Pass a narrower selector once you have a measurable perf problem, or pass `() => null` to opt out at the top level and use `<table.Subscribe>` walls instead.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// Narrow selector — only re-render this component on sorting/pagination changes.
|
|
113
|
+
const table = useTable({ _features, _rowModels, columns, data }, (state) => ({
|
|
114
|
+
sorting: state.sorting,
|
|
115
|
+
pagination: state.pagination,
|
|
116
|
+
}))
|
|
117
|
+
|
|
118
|
+
// Or: subscribe to nothing at the top level; do all reads inside <table.Subscribe>.
|
|
119
|
+
const table = useTable(opts, () => null)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Source: `docs/framework/react/guide/table-state.md`; `examples/react/basic-subscribe/src/main.tsx` (uses `() => null`).
|
|
123
|
+
|
|
124
|
+
### 2. `<table.Subscribe>` and standalone `<Subscribe>`
|
|
125
|
+
|
|
126
|
+
Use `<table.Subscribe>` at the component level. Inside cell/header render contexts, `table` is the core `Table<TFeatures, TData>` (not `ReactTable`), so `table.Subscribe` is **not on the object** — import the standalone `<Subscribe>` and pass `source={table.store}` or `source={table.atoms.X}`.
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { Subscribe } from '@tanstack/react-table'
|
|
130
|
+
|
|
131
|
+
// Component-level: table.Subscribe with a state selector.
|
|
132
|
+
<table.Subscribe selector={(s) => s.pagination}>
|
|
133
|
+
{(pagination) => <span>Page {pagination.pageIndex + 1}</span>}
|
|
134
|
+
</table.Subscribe>
|
|
135
|
+
|
|
136
|
+
// Subscribe to a single atom (narrower than table.store).
|
|
137
|
+
<table.Subscribe source={table.atoms.rowSelection}>
|
|
138
|
+
{(rowSelection) => <span>{Object.keys(rowSelection).length} selected</span>}
|
|
139
|
+
</table.Subscribe>
|
|
140
|
+
|
|
141
|
+
// Inside a cell — table here is the CORE Table, no .Subscribe. Use the import.
|
|
142
|
+
columnHelper.display({
|
|
143
|
+
id: 'select',
|
|
144
|
+
cell: ({ row, table }) => (
|
|
145
|
+
<Subscribe
|
|
146
|
+
source={table.atoms.rowSelection}
|
|
147
|
+
selector={(s) => s[row.id]}
|
|
148
|
+
>
|
|
149
|
+
{(isSelected) => (
|
|
150
|
+
<input
|
|
151
|
+
type="checkbox"
|
|
152
|
+
checked={!!isSelected}
|
|
153
|
+
onChange={row.getToggleSelectedHandler()}
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
</Subscribe>
|
|
157
|
+
),
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Source: `packages/react-table/src/Subscribe.ts`; `examples/react/basic-subscribe/src/main.tsx`.
|
|
162
|
+
|
|
163
|
+
### 3. External atoms with `useCreateAtom` + `options.atoms`
|
|
164
|
+
|
|
165
|
+
Move ownership of any slice to an atom you create with `useCreateAtom` (from `@tanstack/react-store`). Pass it via `options.atoms.<slice>`. The table writes to your atom when you call `table.setSorting(...)`, `table.setPageIndex(...)`, etc. — **no `on*Change` handler is needed**.
|
|
166
|
+
|
|
167
|
+
Precedence: `options.atoms[key]` > `options.state[key]` > internal `baseAtoms[key]`. Don't pass both `state.foo` and `atoms.foo` for the same slice; `atoms` wins silently.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useCreateAtom, useSelector } from '@tanstack/react-store'
|
|
171
|
+
import type { PaginationState, SortingState } from '@tanstack/react-table'
|
|
172
|
+
|
|
173
|
+
function MyTable({ columns, data }) {
|
|
174
|
+
const sortingAtom = useCreateAtom<SortingState>([])
|
|
175
|
+
const paginationAtom = useCreateAtom<PaginationState>({
|
|
176
|
+
pageIndex: 0,
|
|
177
|
+
pageSize: 10,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// Independent fine-grained subscriptions.
|
|
181
|
+
const sorting = useSelector(sortingAtom)
|
|
182
|
+
const pagination = useSelector(paginationAtom)
|
|
183
|
+
|
|
184
|
+
const table = useTable({
|
|
185
|
+
_features,
|
|
186
|
+
_rowModels: {
|
|
187
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
188
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
189
|
+
},
|
|
190
|
+
columns,
|
|
191
|
+
data,
|
|
192
|
+
atoms: { sorting: sortingAtom, pagination: paginationAtom },
|
|
193
|
+
// NOTE: no onSortingChange / onPaginationChange — table writes directly to atoms.
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Source: `examples/react/basic-external-atoms/src/main.tsx`.
|
|
199
|
+
|
|
200
|
+
### 4. `createTableHook` for reusable shared config
|
|
201
|
+
|
|
202
|
+
When you ship the same `_features` / `_rowModels` / cell components across many tables, package them with `createTableHook`. You get `useAppTable`, `createAppColumnHelper`, and `table.AppTable` / `AppHeader` / `AppCell` / `AppFooter` boundaries.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { createTableHook } from '@tanstack/react-table'
|
|
206
|
+
|
|
207
|
+
const { useAppTable, createAppColumnHelper } = createTableHook({
|
|
208
|
+
_features: {},
|
|
209
|
+
_rowModels: {},
|
|
210
|
+
debugTable: true,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const columnHelper = createAppColumnHelper<Person>()
|
|
214
|
+
const columns = columnHelper.columns([
|
|
215
|
+
columnHelper.accessor('firstName', { cell: (info) => info.getValue() }),
|
|
216
|
+
// …
|
|
217
|
+
])
|
|
218
|
+
|
|
219
|
+
function App() {
|
|
220
|
+
const [data] = React.useState(() => [...defaultData])
|
|
221
|
+
const table = useAppTable({ columns, data }, (state) => state)
|
|
222
|
+
return (
|
|
223
|
+
<table>
|
|
224
|
+
<thead>{/* table.FlexRender header={h} */}</thead>
|
|
225
|
+
<tbody>{/* table.FlexRender cell={c} */}</tbody>
|
|
226
|
+
</table>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Source: `examples/react/basic-use-app-table/src/main.tsx`; `packages/react-table/src/createTableHook.tsx`.
|
|
232
|
+
|
|
233
|
+
## Common Mistakes
|
|
234
|
+
|
|
235
|
+
### CRITICAL Reading `table.atoms.X.get()` during render and expecting re-renders
|
|
236
|
+
|
|
237
|
+
Wrong:
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
function Pager({ table }) {
|
|
241
|
+
const pagination = table.atoms.pagination.get() // current-value read, NOT a subscription
|
|
242
|
+
return <span>Page {pagination.pageIndex + 1}</span>
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Correct:
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
function Pager({ table }) {
|
|
250
|
+
return (
|
|
251
|
+
<table.Subscribe
|
|
252
|
+
source={table.atoms.pagination}
|
|
253
|
+
selector={(p) => p.pageIndex}
|
|
254
|
+
>
|
|
255
|
+
{(pageIndex) => <span>Page {pageIndex + 1}</span>}
|
|
256
|
+
</table.Subscribe>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
`.get()` and `table.store.state` are current-value reads, not subscriptions. The component never re-renders when the atom changes.
|
|
262
|
+
Source: `docs/framework/react/guide/table-state.md`; `examples/react/basic-subscribe/src/main.tsx`.
|
|
263
|
+
|
|
264
|
+
### HIGH Passing both `atoms.X` and `state.X` for the same slice
|
|
265
|
+
|
|
266
|
+
Wrong:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
const table = useTable({
|
|
270
|
+
_features,
|
|
271
|
+
_rowModels: {},
|
|
272
|
+
columns,
|
|
273
|
+
data,
|
|
274
|
+
atoms: { pagination: paginationAtom },
|
|
275
|
+
state: { pagination }, // silently ignored
|
|
276
|
+
onPaginationChange: setPagination, // silently ignored
|
|
277
|
+
})
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Correct:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
// Pick exactly one source of truth per slice.
|
|
284
|
+
const table = useTable({
|
|
285
|
+
_features,
|
|
286
|
+
_rowModels: {},
|
|
287
|
+
columns,
|
|
288
|
+
data,
|
|
289
|
+
atoms: { pagination: paginationAtom },
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Precedence is `options.atoms[key]` > `options.state[key]` > internal — `state` is dropped without a warning.
|
|
294
|
+
Source: `docs/framework/react/guide/table-state.md`; `examples/react/basic-external-atoms/src/main.tsx`.
|
|
295
|
+
|
|
296
|
+
### HIGH Using `table.Subscribe` inside a column cell or header render
|
|
297
|
+
|
|
298
|
+
Wrong:
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
cell: ({ row, table }) => (
|
|
302
|
+
<table.Subscribe
|
|
303
|
+
source={table.atoms.rowSelection}
|
|
304
|
+
selector={(s) => s[row.id]}
|
|
305
|
+
>
|
|
306
|
+
{(isSelected) => <input type="checkbox" checked={!!isSelected} />}
|
|
307
|
+
</table.Subscribe>
|
|
308
|
+
)
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Correct:
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import { Subscribe } from '@tanstack/react-table'
|
|
315
|
+
|
|
316
|
+
cell: ({ row, table }) => (
|
|
317
|
+
<Subscribe source={table.atoms.rowSelection} selector={(s) => s[row.id]}>
|
|
318
|
+
{(isSelected) => (
|
|
319
|
+
<input
|
|
320
|
+
type="checkbox"
|
|
321
|
+
checked={!!isSelected}
|
|
322
|
+
onChange={row.getToggleSelectedHandler()}
|
|
323
|
+
/>
|
|
324
|
+
)}
|
|
325
|
+
</Subscribe>
|
|
326
|
+
)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
In cell and header render contexts, `table` is the core `Table<TFeatures, TData>`, not `ReactTable` — `table.Subscribe` is undefined. Use the standalone import.
|
|
330
|
+
Source: `docs/framework/react/guide/table-state.md` (Tips); `packages/react-table/src/Subscribe.ts`.
|
|
331
|
+
|
|
332
|
+
### CRITICAL Creating an atom inside the render body without `useCreateAtom`
|
|
333
|
+
|
|
334
|
+
Wrong:
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
function MyTable() {
|
|
338
|
+
const sortingAtom = createAtom<SortingState>([]) // new atom every render
|
|
339
|
+
useTable({
|
|
340
|
+
_features,
|
|
341
|
+
_rowModels: {},
|
|
342
|
+
columns,
|
|
343
|
+
data,
|
|
344
|
+
atoms: { sorting: sortingAtom },
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Correct:
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
function MyTable() {
|
|
353
|
+
const sortingAtom = useCreateAtom<SortingState>([]) // stable across renders
|
|
354
|
+
useTable({
|
|
355
|
+
_features,
|
|
356
|
+
_rowModels: {},
|
|
357
|
+
columns,
|
|
358
|
+
data,
|
|
359
|
+
atoms: { sorting: sortingAtom },
|
|
360
|
+
})
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
A fresh atom each render unbinds the table from the slice and resets the state to the initial value on every render.
|
|
365
|
+
Source: `examples/react/basic-external-atoms/src/main.tsx`.
|
|
366
|
+
|
|
367
|
+
### HIGH Unstable `data` / `columns` / `_features` references
|
|
368
|
+
|
|
369
|
+
Wrong:
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
function MyTable({ rows }) {
|
|
373
|
+
const _features = tableFeatures({ rowSortingFeature }) // new every render
|
|
374
|
+
const columns = [
|
|
375
|
+
/* … */
|
|
376
|
+
] // new every render
|
|
377
|
+
const table = useTable({
|
|
378
|
+
_features,
|
|
379
|
+
_rowModels: {},
|
|
380
|
+
columns,
|
|
381
|
+
data: rows ?? [],
|
|
382
|
+
})
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Correct:
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
// Module scope — declared once.
|
|
390
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
391
|
+
const columns: ColumnDef<typeof _features, Person>[] = [
|
|
392
|
+
/* … */
|
|
393
|
+
]
|
|
394
|
+
|
|
395
|
+
function MyTable({ rows }) {
|
|
396
|
+
const data = rows ?? EMPTY // EMPTY at module scope
|
|
397
|
+
const table = useTable({ _features, _rowModels: {}, columns, data })
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Internal memoization keys off identity. A new reference each render busts memos and forces full recomputation.
|
|
402
|
+
Source: `docs/framework/react/guide/table-state.md` (FAQ #1).
|
|
403
|
+
|
|
404
|
+
### MEDIUM Premature `Subscribe` / custom selector on small tables
|
|
405
|
+
|
|
406
|
+
Wrong:
|
|
407
|
+
|
|
408
|
+
```tsx
|
|
409
|
+
// 50-row table with Subscribe wrapped around every cell.
|
|
410
|
+
header: ({ table }) => (
|
|
411
|
+
<Subscribe source={table.store} selector={(s) => s.sorting}>
|
|
412
|
+
{() => <SortHeader />}
|
|
413
|
+
</Subscribe>
|
|
414
|
+
)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Correct:
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
// Default selector + inline rendering. Reach for Subscribe later, scoped to actual hotspots.
|
|
421
|
+
const table = useTable({ _features, _rowModels, columns, data })
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Subscribe and narrow selectors are for large / expensive tables where full re-renders measurably hurt. On a small table they only add complexity.
|
|
425
|
+
Source: maintainer guidance (Phase 4).
|
|
426
|
+
|
|
427
|
+
## See Also
|
|
428
|
+
|
|
429
|
+
- `tanstack-table/react/react-subscribe-compiler-compat` — when builder reads in nested components break under React Compiler memoization.
|
|
430
|
+
- `tanstack-table/react/getting-started` — first-table walkthrough.
|
|
431
|
+
- `tanstack-table/react/production-readiness` — narrowing selectors, tree-shaking, reference stability.
|
|
432
|
+
- `tanstack-table/react/compose-with-tanstack-store` — sharing slice atoms across components, persistence.
|