@tanstack/svelte-table 9.0.0-alpha.47 → 9.0.0-alpha.49
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 +7 -5
- package/skills/svelte/client-to-server/SKILL.md +238 -0
- package/skills/svelte/compose-with-tanstack-form/SKILL.md +295 -0
- package/skills/svelte/compose-with-tanstack-pacer/SKILL.md +176 -0
- package/skills/svelte/compose-with-tanstack-query/SKILL.md +299 -0
- package/skills/svelte/compose-with-tanstack-store/SKILL.md +277 -0
- package/skills/svelte/compose-with-tanstack-virtual/SKILL.md +286 -0
- package/skills/svelte/getting-started/SKILL.md +340 -0
- package/skills/svelte/migrate-v8-to-v9/SKILL.md +256 -0
- package/skills/svelte/production-readiness/SKILL.md +256 -0
- package/skills/svelte/table-state/SKILL.md +441 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: svelte/production-readiness
|
|
3
|
+
description: >
|
|
4
|
+
Ship-ready optimizations for `@tanstack/svelte-table@9` on Svelte 5. Tree-shake by registering
|
|
5
|
+
ONLY the `_features` you use; keep `_features`, `columns`, and `data` stable; replace broad
|
|
6
|
+
`(state) => state` selectors with narrow projections on `createTable`; reach for
|
|
7
|
+
`subscribeTable(atom, selector?)` when only one block of markup should react; lean on rune-aware
|
|
8
|
+
atom reads (`table.atoms.<slice>.get()`) for non-reactive paths; key every `{#each}` block on
|
|
9
|
+
stable ids; debounce high-frequency writes with `@tanstack/svelte-pacer`. Svelte 5+ only.
|
|
10
|
+
type: lifecycle
|
|
11
|
+
library: tanstack-table
|
|
12
|
+
framework: svelte
|
|
13
|
+
library_version: '9.0.0-alpha.48'
|
|
14
|
+
requires:
|
|
15
|
+
- setup
|
|
16
|
+
- state-management
|
|
17
|
+
- svelte/table-state
|
|
18
|
+
sources:
|
|
19
|
+
- TanStack/table:docs/guide/features.md
|
|
20
|
+
- TanStack/table:docs/framework/svelte/guide/table-state.md
|
|
21
|
+
- TanStack/table:packages/svelte-table/src/createTable.svelte.ts
|
|
22
|
+
- TanStack/table:packages/svelte-table/src/subscribe.ts
|
|
23
|
+
- TanStack/table:examples/svelte/basic-external-atoms/
|
|
24
|
+
- TanStack/table:examples/svelte/virtualized-rows/
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Production Readiness — Svelte
|
|
28
|
+
|
|
29
|
+
Once your tables work, this is the checklist for making them fast and small. Most of these are
|
|
30
|
+
v9-specific — v8 tables won't have any of these levers.
|
|
31
|
+
|
|
32
|
+
## 1. Register only the features you use
|
|
33
|
+
|
|
34
|
+
`_features` is the bundle gate. Any feature you don't register is tree-shaken out — including
|
|
35
|
+
its state slice, its API surface, and its reactive plumbing.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// good — minimal table, ~smallest bundle
|
|
39
|
+
const _features = tableFeatures({})
|
|
40
|
+
|
|
41
|
+
// good — feature-by-feature opt-in
|
|
42
|
+
const _features = tableFeatures({
|
|
43
|
+
rowPaginationFeature,
|
|
44
|
+
rowSortingFeature,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// bad — kitchen sink, every state slice created even when unused
|
|
48
|
+
const _features = tableFeatures({
|
|
49
|
+
columnFilteringFeature,
|
|
50
|
+
columnGroupingFeature,
|
|
51
|
+
columnOrderingFeature,
|
|
52
|
+
columnPinningFeature,
|
|
53
|
+
columnResizingFeature,
|
|
54
|
+
columnSizingFeature,
|
|
55
|
+
columnVisibilityFeature,
|
|
56
|
+
globalFilteringFeature,
|
|
57
|
+
rowExpandingFeature,
|
|
58
|
+
rowPaginationFeature,
|
|
59
|
+
rowPinningFeature,
|
|
60
|
+
rowSelectionFeature,
|
|
61
|
+
rowSortingFeature,
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If you find yourself running a table without a feature's UI ever showing, drop the feature.
|
|
66
|
+
|
|
67
|
+
## 2. Stable identities for `_features`, `columns`, `data`
|
|
68
|
+
|
|
69
|
+
`createTable` syncs options in `$effect.pre`. If any of these identities flip every component
|
|
70
|
+
run, the table re-syncs more than it needs to.
|
|
71
|
+
|
|
72
|
+
- **`_features` and `_rowModels`**: declare at module scope, not inside the component
|
|
73
|
+
function, and never inside `$derived` / `$effect`.
|
|
74
|
+
- **`columns`**: same — module scope or `$state.frozen` / a non-reactive `const` in the
|
|
75
|
+
component. Reactive recompute of columns is rare and almost always a bug.
|
|
76
|
+
- **`data`**: pass with a getter (`get data()`) so the reference is stable when the data
|
|
77
|
+
doesn't change. If you're reshaping data inside the component, do it once in a `$derived`,
|
|
78
|
+
not on every read.
|
|
79
|
+
|
|
80
|
+
```svelte
|
|
81
|
+
<script lang="ts">
|
|
82
|
+
// module scope is fine in .svelte too, when truly static
|
|
83
|
+
const _features = tableFeatures({ rowPaginationFeature })
|
|
84
|
+
const _rowModels = { paginatedRowModel: createPaginatedRowModel() }
|
|
85
|
+
const columns = columnHelper.columns([
|
|
86
|
+
/* ... */
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
let rawRows = $state<Person[]>([])
|
|
90
|
+
const data = $derived(rawRows.map(normalize)) // computed once per rawRows change
|
|
91
|
+
|
|
92
|
+
const table = createTable({
|
|
93
|
+
_features,
|
|
94
|
+
_rowModels,
|
|
95
|
+
columns,
|
|
96
|
+
get data() {
|
|
97
|
+
return data
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
</script>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 3. Narrow the `table.state` selector
|
|
104
|
+
|
|
105
|
+
The default selector is `(state) => state`. That makes `table.state` re-run any consumer when
|
|
106
|
+
**any** slice changes. Pass a focused selector and you only re-render markup that actually
|
|
107
|
+
depends on that slice.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
// good — only re-projects when pagination changes
|
|
111
|
+
const table = createTable(options, (state) => ({
|
|
112
|
+
pagination: state.pagination,
|
|
113
|
+
}))
|
|
114
|
+
|
|
115
|
+
// even better — only what your UI actually reads
|
|
116
|
+
const table = createTable(options, (state) => ({
|
|
117
|
+
pageIndex: state.pagination.pageIndex,
|
|
118
|
+
pageSize: state.pagination.pageSize,
|
|
119
|
+
}))
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If different parts of the UI need different slices, **don't** widen the selector — use
|
|
123
|
+
`subscribeTable` instead (next section).
|
|
124
|
+
|
|
125
|
+
## 4. Reach for `subscribeTable` for fine-grained reactivity
|
|
126
|
+
|
|
127
|
+
`subscribeTable(source, selector?)` is the per-block subscription. It returns an object whose
|
|
128
|
+
`.current` re-runs only when the selected value changes (shallow compared).
|
|
129
|
+
|
|
130
|
+
```svelte
|
|
131
|
+
<script lang="ts">
|
|
132
|
+
import { subscribeTable } from '@tanstack/svelte-table'
|
|
133
|
+
|
|
134
|
+
// dedicated subscription for the pager
|
|
135
|
+
const pagination = subscribeTable(table.atoms.pagination)
|
|
136
|
+
|
|
137
|
+
// dedicated subscription for a single row's selection state
|
|
138
|
+
const isSelected = subscribeTable(
|
|
139
|
+
table.atoms.rowSelection,
|
|
140
|
+
(rs) => !!rs[row.id],
|
|
141
|
+
)
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<input type="checkbox" checked={isSelected.current} />
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Use it inside per-row components — the row block re-renders only on its own selection toggle,
|
|
148
|
+
not on every row's toggle.
|
|
149
|
+
|
|
150
|
+
## 5. Non-reactive reads where you don't need reactivity
|
|
151
|
+
|
|
152
|
+
Inside event handlers, derived calculations, or one-shot logic, read atoms directly. Cheaper
|
|
153
|
+
than subscribing.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
function exportSelected() {
|
|
157
|
+
const selection = table.atoms.rowSelection.get()
|
|
158
|
+
const selectedIds = Object.keys(selection).filter((id) => selection[id])
|
|
159
|
+
// ...
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`table.store.state` is the same idea for a full snapshot.
|
|
164
|
+
|
|
165
|
+
## 6. Key every `{#each}` block on a stable id
|
|
166
|
+
|
|
167
|
+
Svelte without keys recreates nodes on reorder. Result: lost input focus, lost scroll, lost
|
|
168
|
+
component state. Every TanStack Table loop has a stable id.
|
|
169
|
+
|
|
170
|
+
```svelte
|
|
171
|
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
|
172
|
+
<tr>
|
|
173
|
+
{#each headerGroup.headers as header (header.id)}
|
|
174
|
+
<th>...</th>
|
|
175
|
+
{/each}
|
|
176
|
+
</tr>
|
|
177
|
+
{/each}
|
|
178
|
+
|
|
179
|
+
{#each table.getRowModel().rows as row (row.id)}
|
|
180
|
+
<tr>
|
|
181
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
182
|
+
<td>...</td>
|
|
183
|
+
{/each}
|
|
184
|
+
</tr>
|
|
185
|
+
{/each}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 7. Don't fight `$effect.pre`
|
|
189
|
+
|
|
190
|
+
`createTable` is already syncing options in `$effect.pre`. Don't write a second `$effect`
|
|
191
|
+
that calls `table.setOptions` — it'll race with the built-in sync and may render with stale
|
|
192
|
+
state.
|
|
193
|
+
|
|
194
|
+
If you need to react to an option change with a side effect, put the side effect in your own
|
|
195
|
+
`$effect`, not the option write.
|
|
196
|
+
|
|
197
|
+
## 8. Debounce high-frequency writes
|
|
198
|
+
|
|
199
|
+
Two places will hammer table state at keystroke / pointermove rate:
|
|
200
|
+
|
|
201
|
+
- **Filter inputs.** Wrap `setFilterValue` calls with a debounced callback.
|
|
202
|
+
- **Column resizing.** v9 commits `columnSizing` continuously by default; use the resize-end
|
|
203
|
+
commit mode or debounce.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { createDebouncer } from '@tanstack/svelte-pacer/debouncer'
|
|
207
|
+
|
|
208
|
+
const debouncedSetFilter = createDebouncer(
|
|
209
|
+
(value: string) => column.setFilterValue(value),
|
|
210
|
+
{ wait: 200 },
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
See the `compose-with-tanstack-pacer` skill for full examples.
|
|
215
|
+
|
|
216
|
+
## 9. Virtualization for large datasets
|
|
217
|
+
|
|
218
|
+
`getRowModel().rows.length > ~1000` and rows are simple? Performance is fine without
|
|
219
|
+
virtualization. Above that, or with heavy per-row markup, use `@tanstack/svelte-virtual`. See
|
|
220
|
+
the `compose-with-tanstack-virtual` skill.
|
|
221
|
+
|
|
222
|
+
## 10. Don't ship debug flags
|
|
223
|
+
|
|
224
|
+
`debugTable: true`, `debugRows`, `debugHeaders` all log. Strip them or gate on `import.meta.env.DEV`.
|
|
225
|
+
|
|
226
|
+
## 11. Don't reimplement built-ins
|
|
227
|
+
|
|
228
|
+
The #1 production-readiness regression we see in audits: somebody hand-rolled the thing the
|
|
229
|
+
table already does. If you're writing any of these, register the feature instead.
|
|
230
|
+
|
|
231
|
+
- Hand-rolled sort comparator across rows → `rowSortingFeature` + `createSortedRowModel`
|
|
232
|
+
- Hand-rolled page math (`rows.slice(start, end)`) → `rowPaginationFeature` +
|
|
233
|
+
`createPaginatedRowModel`
|
|
234
|
+
- Hand-rolled selection toggle (`selected[row.id] = !selected[row.id]`) → `rowSelectionFeature`
|
|
235
|
+
- Hand-rolled column hide map → `columnVisibilityFeature`
|
|
236
|
+
- Hand-rolled column resizer → `columnResizingFeature`
|
|
237
|
+
- Hand-rolled debounced filter that doesn't update through `setFilterValue` →
|
|
238
|
+
`columnFilteringFeature` + pacer
|
|
239
|
+
|
|
240
|
+
Each rewrite breaks tree-shaking, breaks the reset APIs, and breaks devtools introspection.
|
|
241
|
+
|
|
242
|
+
## Quick smoke test before shipping
|
|
243
|
+
|
|
244
|
+
- Bundle: does the table chunk match the features you registered? (`pnpm build` and inspect.)
|
|
245
|
+
- DevTools profiler: clicking sort triggers exactly one re-render of the headers and rows,
|
|
246
|
+
not every consumer of `table.state`.
|
|
247
|
+
- Resize / filter: no jank, no per-keystroke server hits (pacer / debounce).
|
|
248
|
+
- Reload: state restored from your atom / URL / storage, no flicker.
|
|
249
|
+
- Stress: 100k-row dataset with virtualization stays interactive.
|
|
250
|
+
|
|
251
|
+
## Related skills
|
|
252
|
+
|
|
253
|
+
- `tanstack-table/svelte/table-state` — selectors, atoms, subscribeTable.
|
|
254
|
+
- `tanstack-table/svelte/compose-with-tanstack-pacer` — debounce patterns.
|
|
255
|
+
- `tanstack-table/svelte/compose-with-tanstack-virtual` — virtualization.
|
|
256
|
+
- `tanstack-table/svelte/compose-with-tanstack-store` — atom ownership patterns.
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: svelte/table-state
|
|
3
|
+
description: >
|
|
4
|
+
Svelte 5 rune-based reactivity for TanStack Table v9. Covers `createTable(options, selector?)`,
|
|
5
|
+
the `table.state` selector projection, fine-grained `subscribeTable(atom, selector?)` with `.current`,
|
|
6
|
+
reading and writing `table.atoms` / `table.baseAtoms`, the `svelteReactivity()` bridge that backs
|
|
7
|
+
readonly atoms with `$derived.by` and writable atoms with `$state`, and the `$effect.pre` option
|
|
8
|
+
sync. State ownership: `initialState`, `state` + `on[State]Change`, or external `atoms` from
|
|
9
|
+
`@tanstack/svelte-store` (`createAtom`, `useSelector`). Svelte 5+ only — no Svelte 3/4 support.
|
|
10
|
+
type: framework
|
|
11
|
+
library: tanstack-table
|
|
12
|
+
framework: svelte
|
|
13
|
+
library_version: '9.0.0-alpha.48'
|
|
14
|
+
requires:
|
|
15
|
+
- state-management
|
|
16
|
+
- setup
|
|
17
|
+
sources:
|
|
18
|
+
- TanStack/table:docs/framework/svelte/svelte-table.md
|
|
19
|
+
- TanStack/table:docs/framework/svelte/guide/table-state.md
|
|
20
|
+
- TanStack/table:packages/svelte-table/src/createTable.svelte.ts
|
|
21
|
+
- TanStack/table:packages/svelte-table/src/createTableHook.svelte.ts
|
|
22
|
+
- TanStack/table:packages/svelte-table/src/reactivity.svelte.ts
|
|
23
|
+
- TanStack/table:packages/svelte-table/src/subscribe.ts
|
|
24
|
+
- TanStack/table:examples/svelte/basic-create-table/
|
|
25
|
+
- TanStack/table:examples/svelte/basic-external-atoms/
|
|
26
|
+
- TanStack/table:examples/svelte/basic-external-state/
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Svelte Table State, `subscribeTable` & `createTableHook`
|
|
30
|
+
|
|
31
|
+
> **TanStack Table is a state-management coordinator for table state.** Understanding how state
|
|
32
|
+
> flows through atoms, runes, and selectors is foundational to everything else you do with the
|
|
33
|
+
> Svelte adapter.
|
|
34
|
+
|
|
35
|
+
## Critical: Svelte 5+ only
|
|
36
|
+
|
|
37
|
+
`@tanstack/svelte-table@9` requires **Svelte 5 or newer**. The adapter is built on runes
|
|
38
|
+
(`$state`, `$derived.by`, `$effect.pre`). For Svelte 3/4 projects, stay on
|
|
39
|
+
`@tanstack/svelte-table@8` — there is no v9 path that supports the legacy stores API.
|
|
40
|
+
|
|
41
|
+
## How v9 state is wired in Svelte
|
|
42
|
+
|
|
43
|
+
A table instance has three (and a half) state surfaces:
|
|
44
|
+
|
|
45
|
+
- `table.baseAtoms.<slice>` — writable atoms created from the resolved initial state.
|
|
46
|
+
- `table.atoms.<slice>` — readonly derived atoms, exposed per registered feature.
|
|
47
|
+
- `table.store` — readonly flat TanStack Store, a derived view of all registered atoms.
|
|
48
|
+
- `table.state` — the value returned by the optional selector passed as the second argument to
|
|
49
|
+
`createTable`. **Svelte-only surface.**
|
|
50
|
+
|
|
51
|
+
The Svelte adapter installs `svelteReactivity()` as the `coreReativityFeature`:
|
|
52
|
+
|
|
53
|
+
| Core concept | Svelte binding |
|
|
54
|
+
| ------------- | --------------- |
|
|
55
|
+
| readonly atom | `$derived.by()` |
|
|
56
|
+
| writable atom | `$state` |
|
|
57
|
+
| subscription | rune `$effect` |
|
|
58
|
+
| option sync | `$effect.pre` |
|
|
59
|
+
| batch | `flushSync` |
|
|
60
|
+
|
|
61
|
+
`createTable` reads reactive option getters inside `$effect.pre` so the table sees fresh data,
|
|
62
|
+
columns, and controlled state **before** the DOM renders — `getRowModel()` is never a frame behind.
|
|
63
|
+
|
|
64
|
+
## Feature-based state — registered features only
|
|
65
|
+
|
|
66
|
+
State slices only exist for the features registered in `_features`. Reading
|
|
67
|
+
`table.atoms.rowSelection` without `rowSelectionFeature` is a TypeScript error and a runtime
|
|
68
|
+
`undefined`. **This is the most common v9 mistake.**
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import {
|
|
72
|
+
createTable,
|
|
73
|
+
rowPaginationFeature,
|
|
74
|
+
rowSortingFeature,
|
|
75
|
+
tableFeatures,
|
|
76
|
+
createPaginatedRowModel,
|
|
77
|
+
createSortedRowModel,
|
|
78
|
+
sortFns,
|
|
79
|
+
} from '@tanstack/svelte-table'
|
|
80
|
+
|
|
81
|
+
const _features = tableFeatures({
|
|
82
|
+
rowPaginationFeature,
|
|
83
|
+
rowSortingFeature,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const table = createTable({
|
|
87
|
+
_features,
|
|
88
|
+
_rowModels: {
|
|
89
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
90
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
91
|
+
},
|
|
92
|
+
columns,
|
|
93
|
+
get data() {
|
|
94
|
+
return data
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
table.atoms.pagination.get() // ok
|
|
99
|
+
table.atoms.sorting.get() // ok
|
|
100
|
+
// table.atoms.rowSelection // TypeScript error
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Reading state — pick the right tool for the job
|
|
104
|
+
|
|
105
|
+
### Current value, no reactivity
|
|
106
|
+
|
|
107
|
+
Read the atom directly. Cheapest path; only reactive when called inside a rune-tracked context.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const sorting = table.atoms.sorting.get()
|
|
111
|
+
const pagination = table.atoms.pagination.get()
|
|
112
|
+
const flat = table.store.state
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Reactive read inside markup — `table.state` selector
|
|
116
|
+
|
|
117
|
+
Pass a TanStack Store selector as the second argument to `createTable`. The selected value is
|
|
118
|
+
exposed as `table.state`. The default selector returns the full registered state.
|
|
119
|
+
|
|
120
|
+
```svelte
|
|
121
|
+
<script lang="ts">
|
|
122
|
+
const table = createTable(
|
|
123
|
+
{
|
|
124
|
+
_features,
|
|
125
|
+
_rowModels: { paginatedRowModel: createPaginatedRowModel() },
|
|
126
|
+
columns,
|
|
127
|
+
get data() {
|
|
128
|
+
return data
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
(state) => ({ pagination: state.pagination }),
|
|
132
|
+
)
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<strong>
|
|
136
|
+
Page {table.state.pagination.pageIndex + 1} of {table.getPageCount()}
|
|
137
|
+
</strong>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Fine-grained — `subscribeTable`
|
|
141
|
+
|
|
142
|
+
`subscribeTable(source, selector?)` wraps a `useSelector` with `shallow` compare and returns an
|
|
143
|
+
object whose `.current` is the selected value. Use it when only one block of markup should
|
|
144
|
+
re-render on a state change.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { subscribeTable } from '@tanstack/svelte-table'
|
|
148
|
+
|
|
149
|
+
const pageIndex = subscribeTable(table.atoms.pagination, (p) => p.pageIndex)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```svelte
|
|
153
|
+
<strong>Page {pageIndex.current + 1}</strong>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Setting state — APIs first, atoms last
|
|
157
|
+
|
|
158
|
+
Use the feature APIs. They handle updaters, external-atom routing, and validation:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
table.nextPage()
|
|
162
|
+
table.previousPage()
|
|
163
|
+
table.setPageIndex(0)
|
|
164
|
+
table.setPageSize(25)
|
|
165
|
+
table.setSorting([{ id: 'age', desc: true }])
|
|
166
|
+
column.toggleVisibility()
|
|
167
|
+
row.toggleSelected()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Direct base-atom writes are a last resort:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
When a slice is owned by an external atom (passed through `atoms`), write to the external atom —
|
|
177
|
+
`table.atoms.<slice>` will read from it, not from `baseAtoms`.
|
|
178
|
+
|
|
179
|
+
## State ownership — three patterns
|
|
180
|
+
|
|
181
|
+
### 1. Initial state only
|
|
182
|
+
|
|
183
|
+
The default: set starting values, let the table own the rest. `initialState` also drives
|
|
184
|
+
`resetSorting()`, `resetPagination()`, etc.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const table = createTable({
|
|
188
|
+
_features,
|
|
189
|
+
_rowModels: {},
|
|
190
|
+
columns,
|
|
191
|
+
get data() {
|
|
192
|
+
return data
|
|
193
|
+
},
|
|
194
|
+
initialState: {
|
|
195
|
+
sorting: [{ id: 'age', desc: true }],
|
|
196
|
+
pagination: { pageIndex: 0, pageSize: 25 },
|
|
197
|
+
},
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 2. External atoms (recommended for shared state)
|
|
202
|
+
|
|
203
|
+
When the app should own a slice, create a stable atom with `createAtom`, pass it through `atoms`,
|
|
204
|
+
and subscribe with `useSelector` or `subscribeTable`. The table writes through the external atom
|
|
205
|
+
on `setPageIndex`, `setSorting`, etc.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { createAtom, useSelector } from '@tanstack/svelte-store'
|
|
209
|
+
import {
|
|
210
|
+
createTable,
|
|
211
|
+
rowPaginationFeature,
|
|
212
|
+
rowSortingFeature,
|
|
213
|
+
tableFeatures,
|
|
214
|
+
type PaginationState,
|
|
215
|
+
type SortingState,
|
|
216
|
+
} from '@tanstack/svelte-table'
|
|
217
|
+
|
|
218
|
+
const _features = tableFeatures({
|
|
219
|
+
rowPaginationFeature,
|
|
220
|
+
rowSortingFeature,
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const sortingAtom = createAtom<SortingState>([])
|
|
224
|
+
const paginationAtom = createAtom<PaginationState>({
|
|
225
|
+
pageIndex: 0,
|
|
226
|
+
pageSize: 10,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const sorting = useSelector(sortingAtom)
|
|
230
|
+
const pagination = useSelector(paginationAtom)
|
|
231
|
+
|
|
232
|
+
const table = createTable({
|
|
233
|
+
_features,
|
|
234
|
+
_rowModels: {},
|
|
235
|
+
columns,
|
|
236
|
+
get data() {
|
|
237
|
+
return data
|
|
238
|
+
},
|
|
239
|
+
atoms: {
|
|
240
|
+
sorting: sortingAtom,
|
|
241
|
+
pagination: paginationAtom,
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
When you use `atoms` for a slice, **do not** pair it with the matching `on[State]Change` callback.
|
|
247
|
+
|
|
248
|
+
### 3. External `state` + `on[State]Change` (migration / simple cases)
|
|
249
|
+
|
|
250
|
+
Classic pattern, still supported. Use Svelte 5 `$state` and getter properties so the table sees
|
|
251
|
+
updates.
|
|
252
|
+
|
|
253
|
+
```svelte
|
|
254
|
+
<script lang="ts">
|
|
255
|
+
import {
|
|
256
|
+
createTable,
|
|
257
|
+
rowPaginationFeature,
|
|
258
|
+
rowSortingFeature,
|
|
259
|
+
tableFeatures,
|
|
260
|
+
type PaginationState,
|
|
261
|
+
type SortingState,
|
|
262
|
+
} from '@tanstack/svelte-table'
|
|
263
|
+
|
|
264
|
+
let sorting: SortingState = $state([])
|
|
265
|
+
let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
|
|
266
|
+
|
|
267
|
+
const table = createTable({
|
|
268
|
+
_features,
|
|
269
|
+
_rowModels: {},
|
|
270
|
+
columns,
|
|
271
|
+
get data() {
|
|
272
|
+
return data
|
|
273
|
+
},
|
|
274
|
+
state: {
|
|
275
|
+
get sorting() {
|
|
276
|
+
return sorting
|
|
277
|
+
},
|
|
278
|
+
get pagination() {
|
|
279
|
+
return pagination
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
onSortingChange: (updater) => {
|
|
283
|
+
sorting = updater instanceof Function ? updater(sorting) : updater
|
|
284
|
+
},
|
|
285
|
+
onPaginationChange: (updater) => {
|
|
286
|
+
pagination = updater instanceof Function ? updater(pagination) : updater
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
</script>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> The v8-style monolithic `onStateChange` is gone in v9. Use per-slice `on[State]Change` or, better,
|
|
293
|
+
> external atoms.
|
|
294
|
+
|
|
295
|
+
### Precedence — do not mix sources
|
|
296
|
+
|
|
297
|
+
External `atoms` win over external `state`. External `state` syncs into the internal base atom.
|
|
298
|
+
For any given slice, pick **one** source of truth. Don't pass `initialState.pagination` and
|
|
299
|
+
`atoms.pagination` and `state.pagination` together.
|
|
300
|
+
|
|
301
|
+
## Rendering — `FlexRender`
|
|
302
|
+
|
|
303
|
+
`FlexRender` handles `header`, `cell`, and `footer` definitions whether they're plain strings,
|
|
304
|
+
Svelte components wrapped with `renderComponent`, or snippets wrapped with `renderSnippet`.
|
|
305
|
+
|
|
306
|
+
```svelte
|
|
307
|
+
<script lang="ts">
|
|
308
|
+
import {
|
|
309
|
+
FlexRender,
|
|
310
|
+
renderComponent,
|
|
311
|
+
renderSnippet,
|
|
312
|
+
} from '@tanstack/svelte-table'
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<thead>
|
|
316
|
+
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
|
317
|
+
<tr>
|
|
318
|
+
{#each headerGroup.headers as header (header.id)}
|
|
319
|
+
<th>
|
|
320
|
+
{#if !header.isPlaceholder}
|
|
321
|
+
<FlexRender {header} />
|
|
322
|
+
{/if}
|
|
323
|
+
</th>
|
|
324
|
+
{/each}
|
|
325
|
+
</tr>
|
|
326
|
+
{/each}
|
|
327
|
+
</thead>
|
|
328
|
+
<tbody>
|
|
329
|
+
{#each table.getRowModel().rows as row (row.id)}
|
|
330
|
+
<tr>
|
|
331
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
332
|
+
<td><FlexRender {cell} /></td>
|
|
333
|
+
{/each}
|
|
334
|
+
</tr>
|
|
335
|
+
{/each}
|
|
336
|
+
</tbody>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Always key `{#each}` blocks on stable ids (`headerGroup.id`, `header.id`, `row.id`, `cell.id`).
|
|
340
|
+
|
|
341
|
+
## `createTableHook` — app-wide composition
|
|
342
|
+
|
|
343
|
+
Create one configured hook per app: shared `_features`, `_rowModels`, defaults, and pre-bound
|
|
344
|
+
component registries.
|
|
345
|
+
|
|
346
|
+
```ts
|
|
347
|
+
import {
|
|
348
|
+
createPaginatedRowModel,
|
|
349
|
+
createSortedRowModel,
|
|
350
|
+
createTableHook,
|
|
351
|
+
rowPaginationFeature,
|
|
352
|
+
rowSortingFeature,
|
|
353
|
+
sortFns,
|
|
354
|
+
tableFeatures,
|
|
355
|
+
} from '@tanstack/svelte-table'
|
|
356
|
+
import TextCell from './cells/TextCell.svelte'
|
|
357
|
+
import SortIndicator from './headers/SortIndicator.svelte'
|
|
358
|
+
|
|
359
|
+
export const {
|
|
360
|
+
createAppTable,
|
|
361
|
+
createAppColumnHelper,
|
|
362
|
+
useTableContext,
|
|
363
|
+
useCellContext,
|
|
364
|
+
useHeaderContext,
|
|
365
|
+
} = createTableHook({
|
|
366
|
+
_features: tableFeatures({ rowPaginationFeature, rowSortingFeature }),
|
|
367
|
+
_rowModels: {
|
|
368
|
+
paginatedRowModel: createPaginatedRowModel(),
|
|
369
|
+
sortedRowModel: createSortedRowModel(sortFns),
|
|
370
|
+
},
|
|
371
|
+
cellComponents: { TextCell },
|
|
372
|
+
headerComponents: { SortIndicator },
|
|
373
|
+
})
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
In components:
|
|
377
|
+
|
|
378
|
+
```svelte
|
|
379
|
+
<script lang="ts">
|
|
380
|
+
import { createAppTable, createAppColumnHelper } from './hooks/table'
|
|
381
|
+
|
|
382
|
+
const columnHelper = createAppColumnHelper<Person>()
|
|
383
|
+
const columns = columnHelper.columns([
|
|
384
|
+
columnHelper.accessor('firstName', { header: 'First' }),
|
|
385
|
+
])
|
|
386
|
+
|
|
387
|
+
const table = createAppTable({
|
|
388
|
+
columns,
|
|
389
|
+
get data() {
|
|
390
|
+
return data
|
|
391
|
+
},
|
|
392
|
+
})
|
|
393
|
+
</script>
|
|
394
|
+
|
|
395
|
+
<table.AppTable>
|
|
396
|
+
{#snippet children()}
|
|
397
|
+
<table>
|
|
398
|
+
<thead>
|
|
399
|
+
{#each table.getHeaderGroups() as group (group.id)}
|
|
400
|
+
<tr>
|
|
401
|
+
{#each group.headers as header (header.id)}
|
|
402
|
+
<table.AppHeader {header}>
|
|
403
|
+
{#snippet children(h)}
|
|
404
|
+
<th><h.SortIndicator /></th>
|
|
405
|
+
{/snippet}
|
|
406
|
+
</table.AppHeader>
|
|
407
|
+
{/each}
|
|
408
|
+
</tr>
|
|
409
|
+
{/each}
|
|
410
|
+
</thead>
|
|
411
|
+
</table>
|
|
412
|
+
{/snippet}
|
|
413
|
+
</table.AppTable>
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Inside custom `cellComponents` / `headerComponents` / `tableComponents`, use `useCellContext()` /
|
|
417
|
+
`useHeaderContext()` / `useTableContext()` instead of prop-drilling.
|
|
418
|
+
|
|
419
|
+
## Common failure modes
|
|
420
|
+
|
|
421
|
+
- **Svelte 4 code with v9 adapter.** Will not run. `$state` / `$derived.by` are Svelte 5 syntax.
|
|
422
|
+
- **`createSvelteTable` import.** v8 name. v9 uses `createTable`. There is no `useSvelteTable`,
|
|
423
|
+
`getCoreRowModel`, `getSortedRowModel`, etc. — those are v8 names too.
|
|
424
|
+
- **Forgetting `tableFeatures()`.** `_features` must come from `tableFeatures({...})` for type
|
|
425
|
+
inference; passing a raw object loses the typed state slice keys.
|
|
426
|
+
- **Forgetting feature registration.** Calling `table.setSorting(...)` without
|
|
427
|
+
`rowSortingFeature` in `_features` is a runtime no-op (the API method won't exist).
|
|
428
|
+
- **Mixing ownership.** `atoms.pagination` + `state.pagination` + `initialState.pagination` is
|
|
429
|
+
ambiguous; the table will not "merge" them the way you expect.
|
|
430
|
+
- **Reactive getters dropped.** If you pass `data` as a plain value instead of a getter, the
|
|
431
|
+
table won't re-render when `data` changes. Always use `get data() { return data }`.
|
|
432
|
+
- **Reimplementing built-ins.** If you're hand-rolling sorting comparators, pagination math, or
|
|
433
|
+
selection toggles, you're skipping `rowSortingFeature` / `rowPaginationFeature` /
|
|
434
|
+
`rowSelectionFeature` and their reset / state APIs. Register the feature instead.
|
|
435
|
+
|
|
436
|
+
## Related skills
|
|
437
|
+
|
|
438
|
+
- `tanstack-table/core/state-management` — atom model, slice precedence, base vs derived atoms.
|
|
439
|
+
- `tanstack-table/svelte/getting-started` — end-to-end first table.
|
|
440
|
+
- `tanstack-table/svelte/compose-with-tanstack-store` — direct atom interop.
|
|
441
|
+
- `tanstack-table/svelte/production-readiness` — selector / subscription tuning.
|