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

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,341 @@
1
+ ---
2
+ name: react/production-readiness
3
+ description: >
4
+ Ship-ready optimizations for `@tanstack/react-table` v9: tree-shake the
5
+ bundle by registering ONLY the `features` you actually use; memoize
6
+ `features`, `data`, and `columns` for stable identity; replace
7
+ `(state) => state` with narrow selectors or per-slice
8
+ `useSelector(table.atoms.<slice>)` subscriptions; and push state-driven
9
+ re-renders down the tree with `<table.Subscribe>` / `<Subscribe>` so the
10
+ expensive table body doesn't re-render every time you toggle a sort
11
+ indicator. Don't over-optimize small tables — the default selector +
12
+ inline rendering is fine until measured perf demands more.
13
+ type: lifecycle
14
+ library: tanstack-table
15
+ framework: react
16
+ library_version: '9.0.0-alpha.48'
17
+ requires:
18
+ - setup
19
+ - state-management
20
+ - react/table-state
21
+ sources:
22
+ - TanStack/table:docs/guide/features.md
23
+ - TanStack/table:docs/framework/react/guide/table-state.md
24
+ - TanStack/table:examples/react/basic-subscribe/src/main.tsx
25
+ - TanStack/table:examples/react/basic-external-atoms/src/main.tsx
26
+ - TanStack/table:examples/react/kitchen-sink/src/main.tsx
27
+ ---
28
+
29
+ This skill builds on `tanstack-table/state-management` and `tanstack-table/react/table-state`. Read those first — `features` tree-shaking and the atom reactivity model are the foundation; this skill is about _which_ of the patterns introduced there you actually need in production.
30
+
31
+ ## When to optimize
32
+
33
+ The default `useTable` selector is `(state) => state` — the component re-renders on any state change. That's correct and ergonomic, and for tables with a few hundred rows and basic features it's the right default. Don't reach for `<Subscribe>` walls or per-slice atom subscriptions until you've **measured** a problem (slow keystrokes in a filter input, dropped frames during scrolling, long-running renders). On small tables the optimization noise costs more than it saves.
34
+
35
+ ## Setup — stable references
36
+
37
+ The biggest single perf win is keeping `features`, `rowModels`, `columns`, and `data` references stable across renders. Internal memoization keys off identity, so a new object every render forces full recomputation.
38
+
39
+ ```tsx
40
+ // ✓ Module scope = stable identity
41
+ const features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
42
+ const rowModels = {
43
+ sortedRowModel: createSortedRowModel(sortFns),
44
+ paginatedRowModel: createPaginatedRowModel(),
45
+ }
46
+ const columnHelper = createColumnHelper<typeof features, Person>()
47
+ const columns = columnHelper.columns([
48
+ columnHelper.accessor('firstName', { header: 'First' }),
49
+ columnHelper.accessor('age', { header: 'Age' }),
50
+ ])
51
+
52
+ // Module-scope empty array for the "no data yet" branch.
53
+ const EMPTY: Person[] = []
54
+
55
+ function MyTable({ data }: { data: Person[] | undefined }) {
56
+ const table = useTable({
57
+ features,
58
+ rowModels,
59
+ columns,
60
+ data: data ?? EMPTY,
61
+ })
62
+ }
63
+ ```
64
+
65
+ ## Core Patterns
66
+
67
+ ### 1. Tree-shake `features` to only what you use
68
+
69
+ Avoid `stockFeatures` in production. A sort-only table is ~6–7kb registered explicitly versus ~15–20kb if you import the whole stock set.
70
+
71
+ ```tsx
72
+ // ✓ Pay only for what you render
73
+ const features = tableFeatures({
74
+ rowSortingFeature,
75
+ rowPaginationFeature,
76
+ })
77
+
78
+ // ✗ Ships filtering, faceting, grouping, pinning, expanding, sizing,
79
+ // resizing, visibility, ordering, row-selection, row-pinning…
80
+ const features = tableFeatures(stockFeatures)
81
+ ```
82
+
83
+ Source: `docs/guide/features.md`; maintainer guidance.
84
+
85
+ ### 2. Narrow the `useTable` selector
86
+
87
+ `(state) => state` re-renders the holding component on any state change. If only one component cares about one slice, pass a narrow selector — or pass `() => null` and rely on `<table.Subscribe>` walls inside.
88
+
89
+ ```tsx
90
+ // Narrow to specific slices at the table level.
91
+ const table = useTable({ features, rowModels, columns, data }, (state) => ({
92
+ sorting: state.sorting,
93
+ pagination: state.pagination,
94
+ }))
95
+
96
+ // Or: opt out completely at the top, subscribe surgically inside.
97
+ const table = useTable(opts, () => null)
98
+ ```
99
+
100
+ Source: `examples/react/basic-subscribe/src/main.tsx` (uses `() => null`).
101
+
102
+ ### 3. Push re-renders down with `<table.Subscribe>`
103
+
104
+ A noisy footer that re-renders on every keystroke in a filter doesn't need to re-render the whole `<TableBody>`. Wrap each consumer in `<table.Subscribe>` with its own selector.
105
+
106
+ ```tsx
107
+ function MyTable({ data, columns }) {
108
+ const table = useTable(
109
+ { features, rowModels, columns, data },
110
+ () => null, // top-level opt-out
111
+ )
112
+ return (
113
+ <>
114
+ <TableBody table={table} /> {/* heavy — keep stable */}
115
+ {/* Footer re-renders only on pagination changes */}
116
+ <table.Subscribe selector={(s) => s.pagination}>
117
+ {(pagination) => <PageFooter pagination={pagination} table={table} />}
118
+ </table.Subscribe>
119
+ </>
120
+ )
121
+ }
122
+ ```
123
+
124
+ Source: `examples/react/basic-subscribe/src/main.tsx`.
125
+
126
+ ### 4. Per-slice `useSelector(table.atoms.<slice>)` for narrowest scope
127
+
128
+ Even narrower than `<table.Subscribe>`: subscribe a leaf component to a single atom. Skips constructing a state snapshot entirely.
129
+
130
+ ```tsx
131
+ import { useSelector } from '@tanstack/react-store'
132
+
133
+ function SelectedCount({ table }) {
134
+ // Re-renders ONLY when rowSelection changes — not sorting / pagination / etc.
135
+ const selection = useSelector(table.atoms.rowSelection)
136
+ return <span>{Object.keys(selection).length} selected</span>
137
+ }
138
+ ```
139
+
140
+ Source: `examples/react/basic-external-atoms/src/main.tsx`.
141
+
142
+ ### 5. React Compiler — read state via `<Subscribe>` in nested components
143
+
144
+ The compiler can't see through the `table` closure, so reads via builder APIs (`column.getIsPinned()`, `row.getIsSelected()`) in memoized child components go stale. Wrap them in `<Subscribe>` (see `tanstack-table/react/react-subscribe-compiler-compat`).
145
+
146
+ ### 6. Virtualization in the deepest possible component
147
+
148
+ Keep `useVirtualizer` in the deepest component (`TableBody`, not `App`). Any state change in the holder of the virtualizer re-runs it and tanks scroll perf. See `tanstack-table/react/compose-with-tanstack-virtual`.
149
+
150
+ ## Common Mistakes
151
+
152
+ ### HIGH Using `stockFeatures` in production
153
+
154
+ Wrong:
155
+
156
+ ```tsx
157
+ import { useTable, stockFeatures, tableFeatures } from '@tanstack/react-table'
158
+ const features = tableFeatures(stockFeatures) // ships every feature
159
+ ```
160
+
161
+ Correct:
162
+
163
+ ```tsx
164
+ import {
165
+ useTable,
166
+ tableFeatures,
167
+ rowSortingFeature,
168
+ rowPaginationFeature,
169
+ } from '@tanstack/react-table'
170
+ const features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
171
+ ```
172
+
173
+ Tree-shaking via `features` is one of the headline reasons for the v9 rewrite. `stockFeatures` exists for migration / "everything on" smoke tests, not production.
174
+ Source: maintainer guidance; `docs/guide/features.md`.
175
+
176
+ ### HIGH Unstable `features` / `rowModels` / `columns` references
177
+
178
+ Wrong:
179
+
180
+ ```tsx
181
+ function MyTable({ data }) {
182
+ const features = tableFeatures({ rowSortingFeature }) // new every render
183
+ const rowModels = { sortedRowModel: createSortedRowModel(sortFns) } // new every render
184
+ const table = useTable({ features, rowModels, columns, data })
185
+ }
186
+ ```
187
+
188
+ Correct:
189
+
190
+ ```tsx
191
+ // Module scope — declared once.
192
+ const features = tableFeatures({ rowSortingFeature })
193
+ const rowModels = { sortedRowModel: createSortedRowModel(sortFns) }
194
+
195
+ function MyTable({ data }) {
196
+ const table = useTable({ features, rowModels, columns, data })
197
+ }
198
+ ```
199
+
200
+ Internal memoization keys off identity. A new object every render busts memos and forces full recomputation.
201
+ Source: FAQ #1; `examples/react/basic-use-table/src/main.tsx`.
202
+
203
+ ### HIGH `data={rows ?? []}` in JSX
204
+
205
+ Wrong:
206
+
207
+ ```tsx
208
+ <MyTable data={query.data?.rows ?? []} columns={columns} />
209
+ ```
210
+
211
+ Correct:
212
+
213
+ ```tsx
214
+ const EMPTY: Person[] = [] // module scope
215
+
216
+ <MyTable data={query.data?.rows ?? EMPTY} columns={columns} />
217
+ // or memoize the fallback:
218
+ const data = React.useMemo(() => query.data?.rows ?? [], [query.data])
219
+ ```
220
+
221
+ The `?? []` produces a new array identity each render, busting internal memos that depend on `data` reference.
222
+ Source: `examples/react/with-tanstack-query/src/main.tsx`.
223
+
224
+ ### MEDIUM Leaving `(state) => state` when only one component cares
225
+
226
+ Wrong:
227
+
228
+ ```tsx
229
+ // Default selector — whole tree re-renders on every state change.
230
+ const table = useTable(opts)
231
+ return <DeeplyNestedTable table={table} />
232
+ ```
233
+
234
+ Correct:
235
+
236
+ ```tsx
237
+ const table = useTable(opts, () => null)
238
+ return (
239
+ <>
240
+ <TableBody table={table} />
241
+ <table.Subscribe selector={(s) => s.pagination}>
242
+ {(p) => <PageFooter pagination={p} />}
243
+ </table.Subscribe>
244
+ </>
245
+ )
246
+ ```
247
+
248
+ Once you've measured a problem, narrow the top selector and add `<table.Subscribe>` walls around the components that actually need state.
249
+ Source: `examples/react/basic-subscribe/src/main.tsx`.
250
+
251
+ ### MEDIUM Subscribing to the whole `table.store` when a single atom would do
252
+
253
+ Wrong:
254
+
255
+ ```tsx
256
+ <table.Subscribe selector={(s) => s.rowSelection}>
257
+ {(rs) => <span>{Object.keys(rs).length} selected</span>}
258
+ </table.Subscribe>
259
+ ```
260
+
261
+ Correct:
262
+
263
+ ```tsx
264
+ import { useSelector } from '@tanstack/react-store'
265
+
266
+ function SelectedCount({ table }) {
267
+ const selection = useSelector(table.atoms.rowSelection)
268
+ return <span>{Object.keys(selection).length} selected</span>
269
+ }
270
+ ```
271
+
272
+ `<table.Subscribe>` still selects from `table.state` (the full state). For a single slice, `useSelector(table.atoms.X)` skips even constructing the snapshot.
273
+ Source: `docs/framework/react/guide/table-state.md`.
274
+
275
+ ### MEDIUM Hoisting heavy table state reads above virtualizers
276
+
277
+ Wrong:
278
+
279
+ ```tsx
280
+ function App() {
281
+ const rowVirtualizer = useVirtualizer({
282
+ /* … */
283
+ }) // virtualizer too high
284
+ const table = useTable(opts)
285
+ return <TableBody table={table} virtualizer={rowVirtualizer} />
286
+ }
287
+ ```
288
+
289
+ Correct:
290
+
291
+ ```tsx
292
+ function App() {
293
+ const tableContainerRef = React.useRef<HTMLDivElement>(null)
294
+ const table = useTable(opts)
295
+ return (
296
+ <div ref={tableContainerRef} style={{ overflow: 'auto', height: 800 }}>
297
+ <TableBody table={table} tableContainerRef={tableContainerRef} />
298
+ </div>
299
+ )
300
+ }
301
+ function TableBody({ table, tableContainerRef }) {
302
+ const rowVirtualizer = useVirtualizer({
303
+ /* … */
304
+ }) // virtualizer at the bottom
305
+ /* … */
306
+ }
307
+ ```
308
+
309
+ The virtualizer in the deepest component avoids re-running on unrelated state changes.
310
+ Source: `examples/react/virtualized-rows/src/main.tsx`.
311
+
312
+ ### MEDIUM Premature `<Subscribe>` / narrow selectors on small tables
313
+
314
+ Wrong:
315
+
316
+ ```tsx
317
+ // 50-row table with Subscribe around every cell.
318
+ header: ({ table }) => (
319
+ <Subscribe source={table.store} selector={(s) => s.sorting}>
320
+ {() => <SortHeader />}
321
+ </Subscribe>
322
+ )
323
+ ```
324
+
325
+ Correct:
326
+
327
+ ```tsx
328
+ const table = useTable({ features, rowModels, columns, data })
329
+ // Reach for Subscribe later, scoped to actual hotspots.
330
+ ```
331
+
332
+ Advanced state-management patterns are for advanced cases. On small tables the boundary churn costs more than it saves.
333
+ Source: maintainer guidance (Phase 4).
334
+
335
+ ## See Also
336
+
337
+ - `tanstack-table/react/table-state` — the API surface this skill optimizes against.
338
+ - `tanstack-table/react/react-subscribe-compiler-compat` — required reading if React Compiler is on.
339
+ - `tanstack-table/react/compose-with-tanstack-store` — fine-grained subscriptions via external atoms.
340
+ - `tanstack-table/react/compose-with-tanstack-virtual` — row/column virtualization patterns.
341
+ - `tanstack-table/react/compose-with-tanstack-devtools` — `/production` import for live devtools in prod.
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: react/react-subscribe-compiler-compat
3
+ description: >
4
+ React Compiler compatibility for `@tanstack/react-table` v9. When you read
5
+ table state via builder APIs (`column.getIsPinned()`, `row.getIsSelected()`,
6
+ `cell.getIsAggregated()`, `header.column.getIsSorted()`) inside a nested
7
+ custom component, React Compiler memoizes the child against the stable
8
+ `column` / `row` / `cell` reference and never re-runs when the underlying
9
+ atom changes. Symptom: stale checkboxes, frozen sort indicators, dead pin
10
+ buttons. Fix: wrap the JSX in `<Subscribe source={table.store} selector={…}>`
11
+ or `<Subscribe source={table.atoms.X}>` so the dependency is visible to the
12
+ compiler. Routing keywords: Subscribe, table.Subscribe, React Compiler,
13
+ stale checkbox, memoized header/cell, builder API.
14
+ type: framework
15
+ library: tanstack-table
16
+ framework: react
17
+ library_version: '9.0.0-alpha.48'
18
+ requires:
19
+ - react/table-state
20
+ sources:
21
+ - TanStack/table:docs/framework/react/guide/table-state.md
22
+ - TanStack/table:packages/react-table/src/Subscribe.ts
23
+ - TanStack/table:examples/react/basic-subscribe/src/main.tsx
24
+ ---
25
+
26
+ This skill builds on `tanstack-table/state-management` and `tanstack-table/react/table-state`. Read those first — the atom model is what makes builder reads invisible to React Compiler, and `table-state` covers the basics of `<Subscribe>` / `<table.Subscribe>`. This skill is laser-focused on the **one** React-specific failure mode that comes up when React Compiler is enabled.
27
+
28
+ ## Why this exists
29
+
30
+ Under React Compiler, JSX is memoized against the props your component receives. A custom `DraggableHeader({ header })` receives a stable `header` reference; the compiler hashes the JSX it produces against that reference. When you call `header.column.getIsPinned()` inside that component, the compiler **cannot see the atom read** hidden behind the method — it returns the cached JSX, and the UI goes stale.
31
+
32
+ The fix is to make the dependency visible: read the slice via `<Subscribe>` or `<table.Subscribe>`. The compiler sees the selector function, picks up the dependency edge, and re-runs the children whenever the subscribed slice changes.
33
+
34
+ ## Setup
35
+
36
+ You only need `<Subscribe>` from `@tanstack/react-table`. It's the same component shown in `table-state`, applied specifically around builder-pattern reads in custom nested components.
37
+
38
+ ```tsx
39
+ import { Subscribe } from '@tanstack/react-table'
40
+ ```
41
+
42
+ ## Core Pattern: wrap nested builder reads in `<Subscribe>`
43
+
44
+ Whenever a child component reads state via a builder method (`getIs*`, `getCan*`, etc.) inside JSX that the compiler memoizes, wrap it in `<Subscribe>` keyed on the relevant slice.
45
+
46
+ ### Pin / sort indicator on a custom header component
47
+
48
+ ```tsx
49
+ import { Subscribe } from '@tanstack/react-table'
50
+
51
+ function DraggableHeader({ header, table }) {
52
+ return (
53
+ <Subscribe
54
+ source={table.store}
55
+ selector={(s) => ({ columnPinning: s.columnPinning, sorting: s.sorting })}
56
+ >
57
+ {() => {
58
+ // Reads run inside the Subscribe child — re-evaluated on selected slice changes.
59
+ const isPinned = header.column.getIsPinned()
60
+ const sortDir = header.column.getIsSorted()
61
+ return (
62
+ <th data-pinned={isPinned}>
63
+ {header.column.id}
64
+ {sortDir === 'asc' ? ' 🔼' : sortDir === 'desc' ? ' 🔽' : null}
65
+ </th>
66
+ )
67
+ }}
68
+ </Subscribe>
69
+ )
70
+ }
71
+ ```
72
+
73
+ Source: `docs/framework/react/guide/table-state.md` (Subscribe for React Compiler Compatibility); `packages/react-table/src/Subscribe.ts`.
74
+
75
+ ### Row-selection checkbox inside a cell — narrowest subscription
76
+
77
+ ```tsx
78
+ columnHelper.display({
79
+ id: 'select',
80
+ cell: ({ row, table }) => (
81
+ // Subscribe to the rowSelection ATOM (not table.store) and project to ONE row.
82
+ // Re-renders ONLY when this row's selection flips.
83
+ <Subscribe
84
+ source={table.atoms.rowSelection}
85
+ selector={(rowSelection) => rowSelection[row.id]}
86
+ >
87
+ {(isSelected) => (
88
+ <input
89
+ type="checkbox"
90
+ checked={!!isSelected}
91
+ disabled={!row.getCanSelect()}
92
+ indeterminate={row.getIsSomeSelected()}
93
+ onChange={row.getToggleSelectedHandler()}
94
+ />
95
+ )}
96
+ </Subscribe>
97
+ ),
98
+ })
99
+ ```
100
+
101
+ Source: `examples/react/basic-subscribe/src/main.tsx` (this exact pattern).
102
+
103
+ ### Component-level vs cell-level — which API
104
+
105
+ | Context | `table` is | API |
106
+ | ------------------------------------------------------------ | ------------------------------ | ---------------------------------------------------------------------------- |
107
+ | Component receiving `table` from `useTable` | `ReactTable<…>` | `<table.Subscribe>` works |
108
+ | Inside `cell: ({ table }) => …` / `header: ({ table }) => …` | core `Table<TFeatures, TData>` | `table.Subscribe` is **undefined**. Use the standalone `<Subscribe>` import. |
109
+
110
+ ## Common Mistakes
111
+
112
+ ### CRITICAL Builder read in a nested component without `<Subscribe>`
113
+
114
+ Wrong:
115
+
116
+ ```tsx
117
+ function DraggableHeader({ header }) {
118
+ const isPinned = header.column.getIsPinned() // hidden atom read
119
+ return <th data-pinned={isPinned}>{header.column.id}</th>
120
+ }
121
+ ```
122
+
123
+ Correct:
124
+
125
+ ```tsx
126
+ import { Subscribe } from '@tanstack/react-table'
127
+
128
+ function DraggableHeader({ header, table }) {
129
+ return (
130
+ <Subscribe source={table.store} selector={(s) => s.columnPinning}>
131
+ {() => {
132
+ const isPinned = header.column.getIsPinned()
133
+ return <th data-pinned={isPinned}>{header.column.id}</th>
134
+ }}
135
+ </Subscribe>
136
+ )
137
+ }
138
+ ```
139
+
140
+ React Compiler memoizes the child's JSX against the stable `header` reference. The state-dependent builder method hides its atom dependency, so the memoized JSX never re-runs.
141
+ Source: `docs/framework/react/guide/table-state.md`; `examples/react/basic-subscribe/src/main.tsx`; `packages/react-table/src/Subscribe.ts`.
142
+
143
+ ### HIGH Using `table.Subscribe` from inside a cell or header definition
144
+
145
+ Wrong:
146
+
147
+ ```tsx
148
+ cell: ({ row, table }) => (
149
+ <table.Subscribe
150
+ source={table.atoms.rowSelection}
151
+ selector={(s) => s[row.id]}
152
+ >
153
+ {(isSelected) => <input type="checkbox" checked={!!isSelected} />}
154
+ </table.Subscribe>
155
+ )
156
+ ```
157
+
158
+ Correct:
159
+
160
+ ```tsx
161
+ import { Subscribe } from '@tanstack/react-table'
162
+
163
+ cell: ({ row, table }) => (
164
+ <Subscribe source={table.atoms.rowSelection} selector={(s) => s[row.id]}>
165
+ {(isSelected) => (
166
+ <input
167
+ type="checkbox"
168
+ checked={!!isSelected}
169
+ onChange={row.getToggleSelectedHandler()}
170
+ />
171
+ )}
172
+ </Subscribe>
173
+ )
174
+ ```
175
+
176
+ Cell and header render contexts type `table` as `Table<TFeatures, TData>`, not `ReactTable` — `table.Subscribe` is undefined. Import the standalone `<Subscribe>`.
177
+ Source: `docs/framework/react/guide/table-state.md` (Tips); `packages/react-table/src/Subscribe.ts`.
178
+
179
+ ### MEDIUM Wrapping every cell in `<Subscribe>` by default
180
+
181
+ Wrong:
182
+
183
+ ```tsx
184
+ // Inline cell that already re-runs on every parent render — wrap is unnecessary.
185
+ {
186
+ row.getVisibleCells().map((cell) => (
187
+ <Subscribe source={table.store} selector={(s) => s.rowSelection}>
188
+ {() => (
189
+ <td>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
190
+ )}
191
+ </Subscribe>
192
+ ))
193
+ }
194
+ ```
195
+
196
+ Correct:
197
+
198
+ ```tsx
199
+ {
200
+ row.getVisibleCells().map((cell) => (
201
+ <td key={cell.id}>
202
+ <table.FlexRender cell={cell} />
203
+ </td>
204
+ ))
205
+ }
206
+ ```
207
+
208
+ `<Subscribe>` is overhead. For inline JSX in the parent component the compiler always re-evaluates on parent re-render, so wrapping adds subscription churn without correctness benefit. Reach for `<Subscribe>` only at custom-component boundaries that the compiler memoizes.
209
+ Source: `docs/framework/react/guide/table-state.md`.
210
+
211
+ ### MEDIUM Subscribing to the whole `table.store` for one row's checkbox
212
+
213
+ Wrong:
214
+
215
+ ```tsx
216
+ <Subscribe source={table.store} selector={(s) => s.rowSelection[row.id]}>
217
+ {(isSelected) => <input type="checkbox" checked={!!isSelected} />}
218
+ </Subscribe>
219
+ ```
220
+
221
+ Correct:
222
+
223
+ ```tsx
224
+ <Subscribe source={table.atoms.rowSelection} selector={(s) => s[row.id]}>
225
+ {(isSelected) => (
226
+ <input
227
+ type="checkbox"
228
+ checked={!!isSelected}
229
+ onChange={row.getToggleSelectedHandler()}
230
+ />
231
+ )}
232
+ </Subscribe>
233
+ ```
234
+
235
+ Every change to `table.store` re-runs the Subscribe child. Subscribing to `table.atoms.rowSelection` (a single slice atom) with a per-row projection limits work to actual selection changes for that row.
236
+ Source: `examples/react/basic-subscribe/src/main.tsx`; `docs/framework/react/guide/table-state.md` (Tips).
237
+
238
+ ### CRITICAL Reading raw state with `table.getState()` on v9 instead of `<Subscribe>`
239
+
240
+ Wrong:
241
+
242
+ ```tsx
243
+ function Toolbar({ table }) {
244
+ // v8 muscle memory — does NOT subscribe in v9.
245
+ const { rowSelection } = table.getState()
246
+ return <div>{Object.keys(rowSelection).length} selected</div>
247
+ }
248
+ ```
249
+
250
+ Correct:
251
+
252
+ ```tsx
253
+ function Toolbar({ table }) {
254
+ return (
255
+ <table.Subscribe selector={(s) => Object.keys(s.rowSelection).length}>
256
+ {(count) => <div>{count} selected</div>}
257
+ </table.Subscribe>
258
+ )
259
+ }
260
+ ```
261
+
262
+ `table.getState()` is a current-value read in v9; it does not subscribe the component. The default `useTable` selector subscribes the parent, but deeply-nested children should opt in explicitly.
263
+ Source: PR #6246; `packages/react-table/src/useTable.ts` JSDoc.
264
+
265
+ ## See Also
266
+
267
+ - `tanstack-table/react/table-state` — base `<Subscribe>` / `<table.Subscribe>` API and external atoms.
268
+ - `tanstack-table/react/production-readiness` — selector narrowing and per-slice `useSelector(table.atoms.X)`.
269
+ - `tanstack-table/react/migrate-v8-to-v9` — replacing `useReactTable` with `useTable` to fix the React Compiler "incompatible library" warning.