@tanstack/preact-table 9.0.0-alpha.46 → 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/dist/reactivity.cjs +1 -0
- package/dist/reactivity.cjs.map +1 -1
- package/dist/reactivity.js +1 -0
- package/dist/reactivity.js.map +1 -1
- package/package.json +6 -4
- package/skills/preact/client-to-server/SKILL.md +294 -0
- package/skills/preact/compose-with-tanstack-form/SKILL.md +230 -0
- package/skills/preact/compose-with-tanstack-pacer/SKILL.md +186 -0
- package/skills/preact/compose-with-tanstack-query/SKILL.md +283 -0
- package/skills/preact/compose-with-tanstack-store/SKILL.md +263 -0
- package/skills/preact/compose-with-tanstack-virtual/SKILL.md +275 -0
- package/skills/preact/getting-started/SKILL.md +371 -0
- package/skills/preact/migrate-v8-to-v9/SKILL.md +322 -0
- package/skills/preact/production-readiness/SKILL.md +278 -0
- package/skills/preact/table-state/SKILL.md +432 -0
- package/skills/preact/table-state/references/advanced-state-patterns.md +93 -0
- package/src/reactivity.ts +1 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: preact/production-readiness
|
|
3
|
+
description: >
|
|
4
|
+
Ship-ready optimizations for `@tanstack/preact-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 `useSelector`
|
|
8
|
+
subscriptions; wrap hot subtrees in `<table.Subscribe>`; and prefer slice
|
|
9
|
+
atoms over `state` + `on*Change` for fine-grained updates. Routing keywords:
|
|
10
|
+
preact-table performance, optimization, tree-shaking, stable refs, Subscribe,
|
|
11
|
+
narrow selector.
|
|
12
|
+
type: lifecycle
|
|
13
|
+
library: tanstack-table
|
|
14
|
+
framework: preact
|
|
15
|
+
library_version: '9.0.0-alpha.47'
|
|
16
|
+
requires:
|
|
17
|
+
- setup
|
|
18
|
+
- state-management
|
|
19
|
+
- preact/table-state
|
|
20
|
+
sources:
|
|
21
|
+
- TanStack/table:docs/guide/features.md
|
|
22
|
+
- TanStack/table:docs/framework/preact/guide/table-state.md
|
|
23
|
+
- TanStack/table:examples/preact/basic-subscribe/src/main.tsx
|
|
24
|
+
- TanStack/table:examples/preact/basic-external-atoms/src/main.tsx
|
|
25
|
+
- TanStack/table:packages/preact-table/src/useTable.ts
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
This skill collects the production-readiness levers for a Preact v9 table. Each one is independent — apply only the ones whose problem you actually have.
|
|
29
|
+
|
|
30
|
+
## 1. Tree-Shake `_features`
|
|
31
|
+
|
|
32
|
+
Only register features the table actually uses. v9's bundle savings come from `_features` controlling which feature code (and which state slices and APIs) get included.
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// Bad — pulls every feature into the bundle even if the UI never uses them.
|
|
36
|
+
const _features = tableFeatures({
|
|
37
|
+
rowPaginationFeature,
|
|
38
|
+
rowSortingFeature,
|
|
39
|
+
rowSelectionFeature,
|
|
40
|
+
columnFilteringFeature,
|
|
41
|
+
globalFilteringFeature,
|
|
42
|
+
columnFacetingFeature,
|
|
43
|
+
globalFacetingFeature,
|
|
44
|
+
columnGroupingFeature,
|
|
45
|
+
rowExpandingFeature,
|
|
46
|
+
columnSizingFeature,
|
|
47
|
+
columnVisibilityFeature,
|
|
48
|
+
columnOrderingFeature,
|
|
49
|
+
columnPinningFeature,
|
|
50
|
+
rowPinningFeature,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Good — feature list matches what the UI exposes.
|
|
54
|
+
const _features = tableFeatures({
|
|
55
|
+
rowPaginationFeature,
|
|
56
|
+
rowSortingFeature,
|
|
57
|
+
rowSelectionFeature,
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Same idea for `_rowModels` — only register the row-model factory for features that need one and that you have registered.
|
|
62
|
+
|
|
63
|
+
Source: `docs/guide/features.md`; `docs/framework/preact/preact-table.md`.
|
|
64
|
+
|
|
65
|
+
## 2. Stable References for `_features`, `columns`, `data`, `_rowModels`
|
|
66
|
+
|
|
67
|
+
Identity drives every internal memo. Declare these at module scope when possible; otherwise wrap with `useMemo`.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Best — module scope. Single allocation.
|
|
71
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
72
|
+
const columns: Array<ColumnDef<typeof _features, Person>> = [
|
|
73
|
+
/* … */
|
|
74
|
+
]
|
|
75
|
+
const EMPTY: Person[] = []
|
|
76
|
+
|
|
77
|
+
function MyTable({ rows }: { rows: Person[] | undefined }) {
|
|
78
|
+
const data = rows ?? EMPTY
|
|
79
|
+
const table = useTable({ _features, _rowModels: {}, columns, data })
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Okay — useMemo for dynamic columns.
|
|
83
|
+
function MyTable({ visibleKeys }: { visibleKeys: string[] }) {
|
|
84
|
+
const columns = useMemo(
|
|
85
|
+
() => visibleKeys.map((k) => columnHelper.accessor(k as any, {})),
|
|
86
|
+
[visibleKeys.join(',')],
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Source: `docs/framework/preact/guide/table-state.md` (FAQ #1).
|
|
92
|
+
|
|
93
|
+
## 3. Narrow `useTable` Selector
|
|
94
|
+
|
|
95
|
+
The default selector `(state) => state` re-renders the component on any registered slice change. Narrow it to just the slices the component reads. The Preact adapter uses `shallow` compare from `@tanstack/preact-store` — projected objects only trigger a render when a member changes.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// All slices — fine for a small table.
|
|
99
|
+
const table = useTable(opts, (state) => state)
|
|
100
|
+
|
|
101
|
+
// Narrow — re-render only on sorting/pagination changes.
|
|
102
|
+
const table = useTable(opts, (state) => ({
|
|
103
|
+
sorting: state.sorting,
|
|
104
|
+
pagination: state.pagination,
|
|
105
|
+
}))
|
|
106
|
+
table.state.pagination
|
|
107
|
+
|
|
108
|
+
// Opt-out at the parent; do subscriptions lower in the tree.
|
|
109
|
+
const table = useTable(opts, () => null)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Source: `examples/preact/basic-subscribe/src/main.tsx`.
|
|
113
|
+
|
|
114
|
+
## 4. Wrap Hot Subtrees in `<table.Subscribe>`
|
|
115
|
+
|
|
116
|
+
Once the parent uses `() => null`, push subscriptions next to the UI that actually reads them. Subscribe to single atoms (`source={table.atoms.X}`) to avoid re-deriving the flat store on unrelated changes.
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
const table = useTable(opts, () => null)
|
|
120
|
+
|
|
121
|
+
// Row body — re-render only when filters/pagination cause the row model to change.
|
|
122
|
+
<table.Subscribe
|
|
123
|
+
selector={(s) => ({
|
|
124
|
+
columnFilters: s.columnFilters,
|
|
125
|
+
globalFilter: s.globalFilter,
|
|
126
|
+
pagination: s.pagination,
|
|
127
|
+
})}
|
|
128
|
+
>
|
|
129
|
+
{() => (
|
|
130
|
+
<tbody>
|
|
131
|
+
{table.getRowModel().rows.map((row) => (
|
|
132
|
+
<tr key={row.id}>{/* … */}</tr>
|
|
133
|
+
))}
|
|
134
|
+
</tbody>
|
|
135
|
+
)}
|
|
136
|
+
</table.Subscribe>
|
|
137
|
+
|
|
138
|
+
// Per-row selection checkbox — narrow to that row's selection bit.
|
|
139
|
+
<table.Subscribe
|
|
140
|
+
source={table.atoms.rowSelection}
|
|
141
|
+
selector={(rs) => rs[row.id]}
|
|
142
|
+
>
|
|
143
|
+
{(isSelected) => (
|
|
144
|
+
<input
|
|
145
|
+
type="checkbox"
|
|
146
|
+
checked={!!isSelected}
|
|
147
|
+
onChange={row.getToggleSelectedHandler()}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
150
|
+
</table.Subscribe>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Source: `examples/preact/basic-subscribe/src/main.tsx`.
|
|
154
|
+
|
|
155
|
+
## 5. Prefer Slice Atoms over `state` + `on*Change`
|
|
156
|
+
|
|
157
|
+
External `state` + `on*Change` re-renders the whole component that owns the `useState`. Slice atoms let `useSelector` / `<table.Subscribe>` subscribe individually.
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// Less granular — every slice change re-renders this component.
|
|
161
|
+
const [sorting, setSorting] = useState<SortingState>([])
|
|
162
|
+
const [pagination, setPagination] = useState<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
163
|
+
useTable({
|
|
164
|
+
/* … */,
|
|
165
|
+
state: { sorting, pagination },
|
|
166
|
+
onSortingChange: setSorting,
|
|
167
|
+
onPaginationChange: setPagination,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// More granular — independent atom subscriptions.
|
|
171
|
+
const sortingAtom = useCreateAtom<SortingState>([])
|
|
172
|
+
const paginationAtom = useCreateAtom<PaginationState>({ pageIndex: 0, pageSize: 10 })
|
|
173
|
+
useTable({ /* … */, atoms: { sorting: sortingAtom, pagination: paginationAtom } })
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Source: `examples/preact/basic-external-atoms/src/main.tsx`.
|
|
177
|
+
|
|
178
|
+
## 6. Set Sensible `initialState` Once
|
|
179
|
+
|
|
180
|
+
Use `initialState` for starting values. Setting state in an effect after mount triggers an extra render.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
const table = useTable({
|
|
184
|
+
_features,
|
|
185
|
+
_rowModels: {
|
|
186
|
+
/* … */
|
|
187
|
+
},
|
|
188
|
+
columns,
|
|
189
|
+
data,
|
|
190
|
+
initialState: {
|
|
191
|
+
pagination: { pageIndex: 0, pageSize: 25 },
|
|
192
|
+
sorting: [{ id: 'createdAt', desc: true }],
|
|
193
|
+
},
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Source: `docs/framework/preact/guide/table-state.md`.
|
|
198
|
+
|
|
199
|
+
## 7. Reach for `createTableHook` for Multi-Table Apps
|
|
200
|
+
|
|
201
|
+
When several screens share the same `_features`, `_rowModels`, and conventions, `createTableHook` centralizes the configuration and lets you ship pre-bound cell/header components. Tables collapse to columns + data.
|
|
202
|
+
|
|
203
|
+
Source: `docs/framework/preact/guide/create-table-hook.md`.
|
|
204
|
+
|
|
205
|
+
## Common Mistakes
|
|
206
|
+
|
|
207
|
+
### CRITICAL `tableFeatures(...)` inside the component body
|
|
208
|
+
|
|
209
|
+
Wrong:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
function MyTable() {
|
|
213
|
+
const _features = tableFeatures({ rowSortingFeature }) // new object every render
|
|
214
|
+
useTable({ _features, _rowModels: {}, columns, data })
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Correct:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
const _features = tableFeatures({ rowSortingFeature }) // module scope
|
|
222
|
+
|
|
223
|
+
function MyTable() {
|
|
224
|
+
useTable({ _features, _rowModels: {}, columns, data })
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A new `_features` reference each render busts every memo that keys off it.
|
|
229
|
+
Source: `docs/framework/preact/guide/table-state.md` (FAQ #1).
|
|
230
|
+
|
|
231
|
+
### CRITICAL Reimplementing built-ins manually
|
|
232
|
+
|
|
233
|
+
Wrong:
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
const sorted = useMemo(() => [...data].sort(/* … */), [data, sorting])
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Correct:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
243
|
+
const table = useTable({
|
|
244
|
+
_features,
|
|
245
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
246
|
+
columns,
|
|
247
|
+
data,
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
v9 ships built-ins for sorting, filtering, pagination, grouping, expanding, faceting, row selection, column visibility/order/pinning/sizing, and row pinning. Hand-rolling these is the #1 AI tell.
|
|
252
|
+
Source: `docs/guide/features.md`.
|
|
253
|
+
|
|
254
|
+
### HIGH `() => state` selector everywhere
|
|
255
|
+
|
|
256
|
+
Wrong: every component using `useTable(opts, (state) => state)` re-renders on any slice change. Fine for small tables; expensive for kitchen-sink screens.
|
|
257
|
+
|
|
258
|
+
Correct: pass a narrow selector or `() => null` at large tables, then `<table.Subscribe>` lower.
|
|
259
|
+
Source: `examples/preact/basic-subscribe/src/main.tsx`.
|
|
260
|
+
|
|
261
|
+
### HIGH New atom per render
|
|
262
|
+
|
|
263
|
+
Wrong: `createAtom(...)` inside the component body.
|
|
264
|
+
|
|
265
|
+
Correct: `useCreateAtom(...)` (or atom at module scope).
|
|
266
|
+
Source: `examples/preact/basic-external-atoms/src/main.tsx`.
|
|
267
|
+
|
|
268
|
+
### MEDIUM Subscribe everywhere on a small table
|
|
269
|
+
|
|
270
|
+
Wrong: a 50-row table with `<Subscribe>` wrapped around every cell. Adds complexity, no measurable win.
|
|
271
|
+
|
|
272
|
+
Correct: default selector + inline rendering. Reach for `Subscribe` after measuring a hotspot.
|
|
273
|
+
|
|
274
|
+
## See Also
|
|
275
|
+
|
|
276
|
+
- `tanstack-table/preact/table-state` — Subscribe / atoms reference.
|
|
277
|
+
- `tanstack-table/preact/migrate-v8-to-v9` — what to replace from v8.
|
|
278
|
+
- `tanstack-table/preact/compose-with-tanstack-pacer` — debouncing high-frequency state writes (filters, resize).
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: preact/table-state
|
|
3
|
+
description: >
|
|
4
|
+
Wiring reactivity for `@tanstack/preact-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, preact-store, table.Subscribe,
|
|
13
|
+
FlexRender.
|
|
14
|
+
type: framework
|
|
15
|
+
library: tanstack-table
|
|
16
|
+
framework: preact
|
|
17
|
+
library_version: '9.0.0-alpha.47'
|
|
18
|
+
requires:
|
|
19
|
+
- state-management
|
|
20
|
+
- setup
|
|
21
|
+
sources:
|
|
22
|
+
- TanStack/table:docs/framework/preact/guide/table-state.md
|
|
23
|
+
- TanStack/table:docs/framework/preact/guide/create-table-hook.md
|
|
24
|
+
- TanStack/table:packages/preact-table/src/useTable.ts
|
|
25
|
+
- TanStack/table:packages/preact-table/src/Subscribe.ts
|
|
26
|
+
- TanStack/table:packages/preact-table/src/FlexRender.tsx
|
|
27
|
+
- TanStack/table:packages/preact-table/src/createTableHook.tsx
|
|
28
|
+
- TanStack/table:examples/preact/basic-use-table/src/main.tsx
|
|
29
|
+
- TanStack/table:examples/preact/basic-subscribe/src/main.tsx
|
|
30
|
+
- TanStack/table:examples/preact/basic-external-atoms/src/main.tsx
|
|
31
|
+
- TanStack/table:examples/preact/basic-external-state/src/main.tsx
|
|
32
|
+
- TanStack/table:examples/preact/basic-use-app-table/src/main.tsx
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
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`). The Preact adapter closely mirrors the React adapter: `useTable` returns a `PreactTable<TFeatures, TData, TSelected>` whose state is backed by TanStack Store atoms, and `<table.Subscribe>` lets components subscribe to slices fine-grained.
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
Every Preact 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>`.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { render } from 'preact'
|
|
43
|
+
import { useState } from 'preact/hooks'
|
|
44
|
+
import {
|
|
45
|
+
createColumnHelper,
|
|
46
|
+
createSortedRowModel,
|
|
47
|
+
rowSortingFeature,
|
|
48
|
+
sortFns,
|
|
49
|
+
tableFeatures,
|
|
50
|
+
useTable,
|
|
51
|
+
} from '@tanstack/preact-table'
|
|
52
|
+
|
|
53
|
+
type Person = { firstName: string; lastName: string; age: number }
|
|
54
|
+
|
|
55
|
+
// Module-scope = stable identity. Critical for re-render perf.
|
|
56
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
57
|
+
const columnHelper = createColumnHelper<typeof _features, Person>()
|
|
58
|
+
|
|
59
|
+
const columns = columnHelper.columns([
|
|
60
|
+
columnHelper.accessor('firstName', { header: 'First' }),
|
|
61
|
+
columnHelper.accessor('lastName', { header: 'Last' }),
|
|
62
|
+
columnHelper.accessor('age', { header: 'Age' }),
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
function PeopleTable({ data }: { data: Person[] }) {
|
|
66
|
+
const table = useTable(
|
|
67
|
+
{
|
|
68
|
+
_features,
|
|
69
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
70
|
+
columns,
|
|
71
|
+
data,
|
|
72
|
+
},
|
|
73
|
+
(state) => state, // default selector
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<table>
|
|
78
|
+
<thead>
|
|
79
|
+
{table.getHeaderGroups().map((hg) => (
|
|
80
|
+
<tr key={hg.id}>
|
|
81
|
+
{hg.headers.map((h) => (
|
|
82
|
+
<th key={h.id} onClick={h.column.getToggleSortingHandler()}>
|
|
83
|
+
{h.isPlaceholder ? null : <table.FlexRender header={h} />}
|
|
84
|
+
</th>
|
|
85
|
+
))}
|
|
86
|
+
</tr>
|
|
87
|
+
))}
|
|
88
|
+
</thead>
|
|
89
|
+
<tbody>
|
|
90
|
+
{table.getRowModel().rows.map((row) => (
|
|
91
|
+
<tr key={row.id}>
|
|
92
|
+
{row.getAllCells().map((cell) => (
|
|
93
|
+
<td key={cell.id}>
|
|
94
|
+
<table.FlexRender cell={cell} />
|
|
95
|
+
</td>
|
|
96
|
+
))}
|
|
97
|
+
</tr>
|
|
98
|
+
))}
|
|
99
|
+
</tbody>
|
|
100
|
+
</table>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Source: `examples/preact/basic-use-table/src/main.tsx`.
|
|
106
|
+
|
|
107
|
+
## Core Patterns
|
|
108
|
+
|
|
109
|
+
### 1. `useTable` selector (second argument)
|
|
110
|
+
|
|
111
|
+
The default selector returns the full `TableState<TFeatures>` — the component re-renders on any registered state slice change. Pass a narrower selector once you have a measurable perf problem, or pass `() => null` to opt the parent out at the top level and use `<table.Subscribe>` walls instead.
|
|
112
|
+
|
|
113
|
+
The Preact adapter uses `useSelector` from `@tanstack/preact-store` with `shallow` compare under the hood.
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// Narrow selector — re-render only on sorting/pagination changes.
|
|
117
|
+
const table = useTable(
|
|
118
|
+
{
|
|
119
|
+
_features,
|
|
120
|
+
_rowModels: {
|
|
121
|
+
/*…*/
|
|
122
|
+
},
|
|
123
|
+
columns,
|
|
124
|
+
data,
|
|
125
|
+
},
|
|
126
|
+
(state) => ({ sorting: state.sorting, pagination: state.pagination }),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
table.state.sorting // typed to the projected shape
|
|
130
|
+
|
|
131
|
+
// Or: subscribe to nothing at the top level; read inside <table.Subscribe>.
|
|
132
|
+
const table = useTable(opts, () => null)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Source: `docs/framework/preact/guide/table-state.md`; `examples/preact/basic-subscribe/src/main.tsx` (uses `() => null`).
|
|
136
|
+
|
|
137
|
+
### 2. `<table.Subscribe>` and standalone `<Subscribe>`
|
|
138
|
+
|
|
139
|
+
Use `<table.Subscribe>` at the component level. Inside cell/header render contexts, `table` is the core `Table<TFeatures, TData>` (not `PreactTable`), so `table.Subscribe` is **not on the object** — import the standalone `<Subscribe>` and pass `source={table.store}` or `source={table.atoms.X}`.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { Subscribe } from '@tanstack/preact-table'
|
|
143
|
+
|
|
144
|
+
// Component-level: selector against table.store.
|
|
145
|
+
<table.Subscribe selector={(s) => s.pagination}>
|
|
146
|
+
{(pagination) => <span>Page {pagination.pageIndex + 1}</span>}
|
|
147
|
+
</table.Subscribe>
|
|
148
|
+
|
|
149
|
+
// Single-atom source — narrower than table.store.
|
|
150
|
+
<table.Subscribe source={table.atoms.rowSelection}>
|
|
151
|
+
{(rowSelection) => <span>{Object.keys(rowSelection).length} selected</span>}
|
|
152
|
+
</table.Subscribe>
|
|
153
|
+
|
|
154
|
+
// Per-row identity projection — re-renders only that row's checkbox.
|
|
155
|
+
<table.Subscribe
|
|
156
|
+
source={table.atoms.rowSelection}
|
|
157
|
+
selector={(rs) => rs[row.id]}
|
|
158
|
+
>
|
|
159
|
+
{(isSelected) => (
|
|
160
|
+
<input type="checkbox" checked={!!isSelected} onChange={row.getToggleSelectedHandler()} />
|
|
161
|
+
)}
|
|
162
|
+
</table.Subscribe>
|
|
163
|
+
|
|
164
|
+
// Inside a cell — table here is the CORE Table, no .Subscribe. Use the import.
|
|
165
|
+
columnHelper.display({
|
|
166
|
+
id: 'select',
|
|
167
|
+
cell: ({ row, table }) => (
|
|
168
|
+
<Subscribe
|
|
169
|
+
source={table.atoms.rowSelection}
|
|
170
|
+
selector={(s) => s[row.id]}
|
|
171
|
+
>
|
|
172
|
+
{(isSelected) => (
|
|
173
|
+
<input
|
|
174
|
+
type="checkbox"
|
|
175
|
+
checked={!!isSelected}
|
|
176
|
+
onChange={row.getToggleSelectedHandler()}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
</Subscribe>
|
|
180
|
+
),
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Source: `packages/preact-table/src/Subscribe.ts`; `examples/preact/basic-subscribe/src/main.tsx`.
|
|
185
|
+
|
|
186
|
+
### 3. External atoms with `useCreateAtom` + `options.atoms`
|
|
187
|
+
|
|
188
|
+
Move ownership of any slice to an atom you create with `useCreateAtom` from `@tanstack/preact-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**.
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
import { useCreateAtom, useSelector } from '@tanstack/preact-store'
|
|
194
|
+
import type { PaginationState, SortingState } from '@tanstack/preact-table'
|
|
195
|
+
|
|
196
|
+
function MyTable({ data }) {
|
|
197
|
+
const sortingAtom = useCreateAtom<SortingState>([])
|
|
198
|
+
const paginationAtom = useCreateAtom<PaginationState>({
|
|
199
|
+
pageIndex: 0,
|
|
200
|
+
pageSize: 10,
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Fine-grained subscriptions independent of the table.
|
|
204
|
+
const sorting = useSelector(sortingAtom)
|
|
205
|
+
const pagination = useSelector(paginationAtom)
|
|
206
|
+
|
|
207
|
+
const table = useTable({
|
|
208
|
+
_features,
|
|
209
|
+
_rowModels: {
|
|
210
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
211
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
212
|
+
},
|
|
213
|
+
columns,
|
|
214
|
+
data,
|
|
215
|
+
atoms: { sorting: sortingAtom, pagination: paginationAtom },
|
|
216
|
+
// NOTE: no onSortingChange / onPaginationChange — table writes directly to atoms.
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Source: `examples/preact/basic-external-atoms/src/main.tsx`.
|
|
222
|
+
|
|
223
|
+
### 4. External state with `state` + `on*Change` and `createTableHook`
|
|
224
|
+
|
|
225
|
+
Classic `useState` + `on*Change` integration (v8 migration paths) and the `createTableHook` factory for packaging shared `_features` / `_rowModels` / cell components into `useAppTable` + `createAppColumnHelper` + `table.AppTable` / `AppHeader` / `AppCell` / `AppFooter` boundaries — see [advanced-state-patterns.md](references/advanced-state-patterns.md).
|
|
226
|
+
|
|
227
|
+
## Common Mistakes
|
|
228
|
+
|
|
229
|
+
### CRITICAL Reading `table.atoms.X.get()` during render and expecting re-renders
|
|
230
|
+
|
|
231
|
+
Wrong:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
function Pager({ table }) {
|
|
235
|
+
const pagination = table.atoms.pagination.get() // current-value read, NOT a subscription
|
|
236
|
+
return <span>Page {pagination.pageIndex + 1}</span>
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Correct:
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
function Pager({ table }) {
|
|
244
|
+
return (
|
|
245
|
+
<table.Subscribe
|
|
246
|
+
source={table.atoms.pagination}
|
|
247
|
+
selector={(p) => p.pageIndex}
|
|
248
|
+
>
|
|
249
|
+
{(pageIndex) => <span>Page {pageIndex + 1}</span>}
|
|
250
|
+
</table.Subscribe>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`.get()` and `table.store.state` are current-value reads, not subscriptions. The component never re-renders when the atom changes.
|
|
256
|
+
Source: `docs/framework/preact/guide/table-state.md`.
|
|
257
|
+
|
|
258
|
+
### HIGH Passing both `atoms.X` and `state.X` for the same slice
|
|
259
|
+
|
|
260
|
+
Wrong:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
const table = useTable({
|
|
264
|
+
_features,
|
|
265
|
+
_rowModels: {},
|
|
266
|
+
columns,
|
|
267
|
+
data,
|
|
268
|
+
atoms: { pagination: paginationAtom },
|
|
269
|
+
state: { pagination }, // silently ignored
|
|
270
|
+
onPaginationChange: setPagination, // silently ignored
|
|
271
|
+
})
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Correct:
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
const table = useTable({
|
|
278
|
+
_features,
|
|
279
|
+
_rowModels: {},
|
|
280
|
+
columns,
|
|
281
|
+
data,
|
|
282
|
+
atoms: { pagination: paginationAtom },
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Precedence is `options.atoms[key]` > `options.state[key]` > internal. `state` is dropped without a warning when `atoms` is provided for the same slice.
|
|
287
|
+
Source: `docs/framework/preact/guide/table-state.md`.
|
|
288
|
+
|
|
289
|
+
### HIGH Using `table.Subscribe` inside a column cell or header render
|
|
290
|
+
|
|
291
|
+
Wrong:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
cell: ({ row, table }) => (
|
|
295
|
+
<table.Subscribe
|
|
296
|
+
source={table.atoms.rowSelection}
|
|
297
|
+
selector={(s) => s[row.id]}
|
|
298
|
+
>
|
|
299
|
+
{(isSelected) => <input type="checkbox" checked={!!isSelected} />}
|
|
300
|
+
</table.Subscribe>
|
|
301
|
+
)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Correct:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import { Subscribe } from '@tanstack/preact-table'
|
|
308
|
+
|
|
309
|
+
cell: ({ row, table }) => (
|
|
310
|
+
<Subscribe source={table.atoms.rowSelection} selector={(s) => s[row.id]}>
|
|
311
|
+
{(isSelected) => (
|
|
312
|
+
<input
|
|
313
|
+
type="checkbox"
|
|
314
|
+
checked={!!isSelected}
|
|
315
|
+
onChange={row.getToggleSelectedHandler()}
|
|
316
|
+
/>
|
|
317
|
+
)}
|
|
318
|
+
</Subscribe>
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
In cell and header render contexts, `table` is the core `Table<TFeatures, TData>`, not `PreactTable` — `table.Subscribe` is undefined. Use the standalone import.
|
|
323
|
+
Source: `packages/preact-table/src/Subscribe.ts`.
|
|
324
|
+
|
|
325
|
+
### CRITICAL Creating an atom inside the render body without `useCreateAtom`
|
|
326
|
+
|
|
327
|
+
Wrong:
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
function MyTable() {
|
|
331
|
+
const sortingAtom = createAtom<SortingState>([]) // new atom every render
|
|
332
|
+
useTable({
|
|
333
|
+
_features,
|
|
334
|
+
_rowModels: {},
|
|
335
|
+
columns,
|
|
336
|
+
data,
|
|
337
|
+
atoms: { sorting: sortingAtom },
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Correct:
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
function MyTable() {
|
|
346
|
+
const sortingAtom = useCreateAtom<SortingState>([]) // stable across renders
|
|
347
|
+
useTable({
|
|
348
|
+
_features,
|
|
349
|
+
_rowModels: {},
|
|
350
|
+
columns,
|
|
351
|
+
data,
|
|
352
|
+
atoms: { sorting: sortingAtom },
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
A fresh atom each render unbinds the table from the slice and resets the state to the initial value on every render.
|
|
358
|
+
Source: `examples/preact/basic-external-atoms/src/main.tsx`.
|
|
359
|
+
|
|
360
|
+
### HIGH Unstable `data` / `columns` / `_features` references
|
|
361
|
+
|
|
362
|
+
Wrong:
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
function MyTable({ rows }) {
|
|
366
|
+
const _features = tableFeatures({ rowSortingFeature }) // new every render
|
|
367
|
+
const columns = [
|
|
368
|
+
/* … */
|
|
369
|
+
] // new every render
|
|
370
|
+
const table = useTable({
|
|
371
|
+
_features,
|
|
372
|
+
_rowModels: {},
|
|
373
|
+
columns,
|
|
374
|
+
data: rows ?? [],
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Correct:
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
// Module scope — declared once.
|
|
383
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
384
|
+
const columns: ColumnDef<typeof _features, Person>[] = [
|
|
385
|
+
/* … */
|
|
386
|
+
]
|
|
387
|
+
const EMPTY: Person[] = []
|
|
388
|
+
|
|
389
|
+
function MyTable({ rows }) {
|
|
390
|
+
const data = rows ?? EMPTY
|
|
391
|
+
const table = useTable({ _features, _rowModels: {}, columns, data })
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Internal memoization keys off identity. A new reference each render busts memos and forces full recomputation.
|
|
396
|
+
Source: `docs/framework/preact/guide/table-state.md` (FAQ #1).
|
|
397
|
+
|
|
398
|
+
### HIGH Reimplementing built-in feature logic by hand
|
|
399
|
+
|
|
400
|
+
Wrong:
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
// Re-sorting rows manually outside the table — duplicates rowSortingFeature work.
|
|
404
|
+
const sorted = useMemo(() => [...data].sort(/* … */), [data, sorting])
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Correct:
|
|
408
|
+
|
|
409
|
+
```tsx
|
|
410
|
+
const _features = tableFeatures({ rowSortingFeature })
|
|
411
|
+
const table = useTable({
|
|
412
|
+
_features,
|
|
413
|
+
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
|
|
414
|
+
columns,
|
|
415
|
+
data,
|
|
416
|
+
})
|
|
417
|
+
const rows = table.getRowModel().rows // already sorted
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
TanStack Table v9 ships built-ins for sorting, filtering, pagination, grouping, expanding, faceting, row selection, column visibility/order/pinning/sizing, and row pinning. Register the matching `*Feature` in `_features`, register its row-model factory in `_rowModels`, and call the feature APIs (`setSorting`, `setColumnFilters`, etc.). Re-implementing these by hand is the #1 AI tell.
|
|
421
|
+
Source: `docs/guide/features.md`.
|
|
422
|
+
|
|
423
|
+
## See Also
|
|
424
|
+
|
|
425
|
+
- `tanstack-table/preact/getting-started` — first-table walkthrough.
|
|
426
|
+
- `tanstack-table/preact/migrate-v8-to-v9` — mechanical v8 → v9 breaking changes.
|
|
427
|
+
- `tanstack-table/preact/production-readiness` — narrowing selectors, tree-shaking, reference stability.
|
|
428
|
+
- `tanstack-table/preact/compose-with-tanstack-store` — sharing slice atoms across components, persistence.
|
|
429
|
+
|
|
430
|
+
## References
|
|
431
|
+
|
|
432
|
+
- [advanced-state-patterns.md](references/advanced-state-patterns.md) — `state` + `on*Change` external state and `createTableHook` for reusable shared config
|