@tanstack/svelte-table 9.0.0-beta.7 → 9.0.0-beta.71
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 +5 -1
- package/dist/FlexRender.svelte +15 -1
- package/dist/createTable.svelte.d.ts +26 -36
- package/dist/createTable.svelte.js +27 -32
- package/dist/createTableHook.svelte.d.ts +51 -18
- package/dist/createTableHook.svelte.js +18 -10
- package/dist/experimental-worker-plugin.d.ts +1 -0
- package/dist/experimental-worker-plugin.js +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +0 -1
- package/dist/reactivity.svelte.d.ts +3 -1
- package/dist/reactivity.svelte.js +6 -1
- package/dist/render-component.js +4 -0
- package/package.json +16 -9
- package/skills/create-table-hook/SKILL.md +168 -0
- package/skills/getting-started/SKILL.md +172 -0
- package/skills/migrate-v8-to-v9/SKILL.md +201 -0
- package/skills/table-state/SKILL.md +262 -0
- package/skills/with-tanstack-query/SKILL.md +149 -0
- package/skills/with-tanstack-virtual/SKILL.md +150 -0
- package/dist/subscribe.d.ts +0 -24
- package/dist/subscribe.js +0 -4
- package/skills/svelte/client-to-server/SKILL.md +0 -238
- package/skills/svelte/compose-with-tanstack-form/SKILL.md +0 -295
- package/skills/svelte/compose-with-tanstack-pacer/SKILL.md +0 -176
- package/skills/svelte/compose-with-tanstack-query/SKILL.md +0 -299
- package/skills/svelte/compose-with-tanstack-store/SKILL.md +0 -277
- package/skills/svelte/compose-with-tanstack-virtual/SKILL.md +0 -286
- package/skills/svelte/getting-started/SKILL.md +0 -340
- package/skills/svelte/migrate-v8-to-v9/SKILL.md +0 -256
- package/skills/svelte/production-readiness/SKILL.md +0 -256
- package/skills/svelte/table-state/SKILL.md +0 -441
- package/src/AppCell.svelte +0 -13
- package/src/AppHeader.svelte +0 -13
- package/src/AppTable.svelte +0 -11
- package/src/FlexRender.svelte +0 -103
- package/src/context-keys.ts +0 -3
- package/src/createTable.svelte.ts +0 -137
- package/src/createTableHook.svelte.ts +0 -639
- package/src/createTableState.svelte.ts +0 -30
- package/src/flex-render.ts +0 -3
- package/src/global.d.ts +0 -1
- package/src/index.ts +0 -21
- package/src/merge-objects.ts +0 -79
- package/src/reactivity.svelte.ts +0 -118
- package/src/render-component.ts +0 -107
- package/src/static-functions.ts +0 -1
- package/src/subscribe.ts +0 -46
|
@@ -1,299 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: svelte/compose-with-tanstack-query
|
|
3
|
-
description: >
|
|
4
|
-
Server-side / async data flow with `@tanstack/svelte-query` and `@tanstack/svelte-table`.
|
|
5
|
-
Key the `createQuery` on the table state that drives the request (pagination + sort +
|
|
6
|
-
filters), pass `placeholderData: keepPreviousData` to avoid a "0 rows flash" between pages,
|
|
7
|
-
set `manualPagination` (and optionally `manualSorting` / `manualFiltering`), supply
|
|
8
|
-
`rowCount`, and feed the query result through reactive getters (`get data()`,
|
|
9
|
-
`get rowCount()`). Own driver state with `$state` or `@tanstack/svelte-store` atoms.
|
|
10
|
-
Svelte 5+ only.
|
|
11
|
-
type: composition
|
|
12
|
-
library: tanstack-table
|
|
13
|
-
framework: svelte
|
|
14
|
-
library_version: '9.0.0-alpha.48'
|
|
15
|
-
requires:
|
|
16
|
-
- svelte/client-to-server
|
|
17
|
-
- pagination
|
|
18
|
-
- state-management
|
|
19
|
-
sources:
|
|
20
|
-
- TanStack/table:examples/svelte/with-tanstack-query/
|
|
21
|
-
- TanStack/table:docs/framework/svelte/guide/table-state.md
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
# Compose with TanStack Query (Svelte)
|
|
25
|
-
|
|
26
|
-
`@tanstack/svelte-query` and `@tanstack/svelte-table` complement each other naturally:
|
|
27
|
-
|
|
28
|
-
- **Query** owns server data — fetching, caching, retries, placeholder data.
|
|
29
|
-
- **Table** owns view state — pagination, sort, filters, selection.
|
|
30
|
-
|
|
31
|
-
The integration is short and predictable: drive the query key from the view state, manual-mode
|
|
32
|
-
the affected pipeline stages, pipe the result back through reactive getters.
|
|
33
|
-
|
|
34
|
-
## The pattern in 30 seconds
|
|
35
|
-
|
|
36
|
-
```svelte
|
|
37
|
-
<script lang="ts">
|
|
38
|
-
import { createQuery, keepPreviousData } from '@tanstack/svelte-query'
|
|
39
|
-
import {
|
|
40
|
-
createTable,
|
|
41
|
-
rowPaginationFeature,
|
|
42
|
-
tableFeatures,
|
|
43
|
-
type PaginationState,
|
|
44
|
-
} from '@tanstack/svelte-table'
|
|
45
|
-
|
|
46
|
-
const features = tableFeatures({ rowPaginationFeature })
|
|
47
|
-
|
|
48
|
-
let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
|
|
49
|
-
|
|
50
|
-
const dataQuery = createQuery<{
|
|
51
|
-
rows: Array<Person>
|
|
52
|
-
rowCount: number
|
|
53
|
-
}>(() => ({
|
|
54
|
-
queryKey: ['people', pagination],
|
|
55
|
-
queryFn: () => fetchPeople(pagination),
|
|
56
|
-
placeholderData: keepPreviousData,
|
|
57
|
-
}))
|
|
58
|
-
|
|
59
|
-
const table = createTable({
|
|
60
|
-
features,
|
|
61
|
-
rowModels: {},
|
|
62
|
-
columns,
|
|
63
|
-
get data() {
|
|
64
|
-
return dataQuery.data?.rows ?? []
|
|
65
|
-
},
|
|
66
|
-
get rowCount() {
|
|
67
|
-
return dataQuery.data?.rowCount
|
|
68
|
-
},
|
|
69
|
-
state: {
|
|
70
|
-
get pagination() {
|
|
71
|
-
return pagination
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
onPaginationChange: (updater) => {
|
|
75
|
-
pagination = typeof updater === 'function' ? updater(pagination) : updater
|
|
76
|
-
},
|
|
77
|
-
manualPagination: true,
|
|
78
|
-
})
|
|
79
|
-
</script>
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Three things to notice:
|
|
83
|
-
|
|
84
|
-
1. `queryKey` includes the driver state (`pagination`). Query re-fetches when the page or page
|
|
85
|
-
size changes.
|
|
86
|
-
2. `placeholderData: keepPreviousData` keeps the previous page visible while the next page
|
|
87
|
-
loads. Without it, `dataQuery.data?.rows` is `undefined` for one tick on every page change
|
|
88
|
-
and the table flashes empty.
|
|
89
|
-
3. `manualPagination: true` tells the table the data is already paged. Without `rowCount` the
|
|
90
|
-
pager has no idea how many pages exist.
|
|
91
|
-
|
|
92
|
-
## Driver-state ownership choices
|
|
93
|
-
|
|
94
|
-
You can drive the query from either:
|
|
95
|
-
|
|
96
|
-
- **Component `$state` + `state` + `on[State]Change`** (shown above) — simplest, mirrors
|
|
97
|
-
what most v8 codebases look like after migration.
|
|
98
|
-
- **External `@tanstack/svelte-store` atoms + `atoms`** — preferable when the same state
|
|
99
|
-
drives multiple components (a toolbar, a sidebar, a URL syncer).
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import { createAtom, useSelector } from '@tanstack/svelte-store'
|
|
103
|
-
|
|
104
|
-
const paginationAtom = createAtom<PaginationState>({
|
|
105
|
-
pageIndex: 0,
|
|
106
|
-
pageSize: 10,
|
|
107
|
-
})
|
|
108
|
-
const pagination = useSelector(paginationAtom)
|
|
109
|
-
|
|
110
|
-
const dataQuery = createQuery(() => ({
|
|
111
|
-
queryKey: ['people', pagination.current],
|
|
112
|
-
queryFn: () => fetchPeople(pagination.current),
|
|
113
|
-
placeholderData: keepPreviousData,
|
|
114
|
-
}))
|
|
115
|
-
|
|
116
|
-
const table = createTable({
|
|
117
|
-
features,
|
|
118
|
-
rowModels: {},
|
|
119
|
-
columns,
|
|
120
|
-
get data() {
|
|
121
|
-
return dataQuery.data?.rows ?? []
|
|
122
|
-
},
|
|
123
|
-
get rowCount() {
|
|
124
|
-
return dataQuery.data?.rowCount
|
|
125
|
-
},
|
|
126
|
-
atoms: { pagination: paginationAtom },
|
|
127
|
-
manualPagination: true,
|
|
128
|
-
})
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
`table.setPageIndex(2)` writes through `paginationAtom`, which invalidates `queryKey`, which
|
|
132
|
-
fetches page 3.
|
|
133
|
-
|
|
134
|
-
## Adding sort and filters
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
import type { ColumnFiltersState, SortingState } from '@tanstack/svelte-table'
|
|
138
|
-
|
|
139
|
-
let sorting: SortingState = $state([])
|
|
140
|
-
let filters: ColumnFiltersState = $state([])
|
|
141
|
-
let pagination: PaginationState = $state({ pageIndex: 0, pageSize: 10 })
|
|
142
|
-
|
|
143
|
-
const dataQuery = createQuery(() => ({
|
|
144
|
-
queryKey: ['people', pagination, sorting, filters],
|
|
145
|
-
queryFn: () => fetchPeople({ pagination, sorting, filters }),
|
|
146
|
-
placeholderData: keepPreviousData,
|
|
147
|
-
}))
|
|
148
|
-
|
|
149
|
-
const table = createTable({
|
|
150
|
-
features: tableFeatures({
|
|
151
|
-
rowPaginationFeature,
|
|
152
|
-
rowSortingFeature,
|
|
153
|
-
columnFilteringFeature,
|
|
154
|
-
}),
|
|
155
|
-
rowModels: {},
|
|
156
|
-
columns,
|
|
157
|
-
get data() {
|
|
158
|
-
return dataQuery.data?.rows ?? []
|
|
159
|
-
},
|
|
160
|
-
get rowCount() {
|
|
161
|
-
return dataQuery.data?.rowCount
|
|
162
|
-
},
|
|
163
|
-
state: {
|
|
164
|
-
get pagination() {
|
|
165
|
-
return pagination
|
|
166
|
-
},
|
|
167
|
-
get sorting() {
|
|
168
|
-
return sorting
|
|
169
|
-
},
|
|
170
|
-
get columnFilters() {
|
|
171
|
-
return filters
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
onPaginationChange: (u) =>
|
|
175
|
-
(pagination = typeof u === 'function' ? u(pagination) : u),
|
|
176
|
-
onSortingChange: (u) => (sorting = typeof u === 'function' ? u(sorting) : u),
|
|
177
|
-
onColumnFiltersChange: (u) =>
|
|
178
|
-
(filters = typeof u === 'function' ? u(filters) : u),
|
|
179
|
-
manualPagination: true,
|
|
180
|
-
manualSorting: true,
|
|
181
|
-
manualFiltering: true,
|
|
182
|
-
})
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Reset page on filter / sort change
|
|
186
|
-
|
|
187
|
-
Otherwise a user filters from "all 5000 people" to "5 named Alice" and stays on page 12.
|
|
188
|
-
|
|
189
|
-
```ts
|
|
190
|
-
$effect(() => {
|
|
191
|
-
// re-run on identity change
|
|
192
|
-
filters
|
|
193
|
-
sorting
|
|
194
|
-
table.setPageIndex(0)
|
|
195
|
-
})
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
## Debounce keystroke-driven filters
|
|
199
|
-
|
|
200
|
-
Without debouncing, a search input fires one request per character.
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
import { createDebouncer } from '@tanstack/svelte-pacer/debouncer'
|
|
204
|
-
|
|
205
|
-
const debouncedSetGlobalFilter = createDebouncer(
|
|
206
|
-
(value: string) => table.setGlobalFilter(value),
|
|
207
|
-
{ wait: 250 },
|
|
208
|
-
)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
```svelte
|
|
212
|
-
<input
|
|
213
|
-
oninput={(e) => debouncedSetGlobalFilter.maybeExecute(e.currentTarget.value)}
|
|
214
|
-
/>
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
See the `compose-with-tanstack-pacer` skill for the full pacer pattern.
|
|
218
|
-
|
|
219
|
-
## Loading and empty states
|
|
220
|
-
|
|
221
|
-
`createQuery` exposes `isFetching`, `isPending`, `isError`, `data`. Use them around the
|
|
222
|
-
table, not inside the row loop.
|
|
223
|
-
|
|
224
|
-
```svelte
|
|
225
|
-
{#if dataQuery.isPending}
|
|
226
|
-
<div>Loading…</div>
|
|
227
|
-
{:else if dataQuery.isError}
|
|
228
|
-
<div>Failed: {dataQuery.error.message}</div>
|
|
229
|
-
{:else}
|
|
230
|
-
<table>...</table>
|
|
231
|
-
{/if}
|
|
232
|
-
|
|
233
|
-
{#if dataQuery.isFetching}
|
|
234
|
-
<small>Refreshing…</small>
|
|
235
|
-
{/if}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
`isFetching` is helpful for the "loading next page" indicator while
|
|
239
|
-
`placeholderData: keepPreviousData` still shows the old rows.
|
|
240
|
-
|
|
241
|
-
## Optimistic updates (when you also mutate)
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { createMutation, useQueryClient } from '@tanstack/svelte-query'
|
|
245
|
-
|
|
246
|
-
const queryClient = useQueryClient()
|
|
247
|
-
|
|
248
|
-
const updatePerson = createMutation(() => ({
|
|
249
|
-
mutationFn: (input: Partial<Person>) =>
|
|
250
|
-
fetch(`/api/people/${input.id}`, {
|
|
251
|
-
method: 'PATCH',
|
|
252
|
-
body: JSON.stringify(input),
|
|
253
|
-
}),
|
|
254
|
-
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['people'] }),
|
|
255
|
-
}))
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
After the mutation succeeds, `invalidateQueries` re-fetches; `placeholderData` keeps the old
|
|
259
|
-
rows visible during the refresh.
|
|
260
|
-
|
|
261
|
-
## SvelteKit `load` integration (a sketch)
|
|
262
|
-
|
|
263
|
-
If your table is on a SvelteKit page, `+page.ts` can hydrate the query cache with the first
|
|
264
|
-
page so SSR renders rows immediately. Subsequent pages still go through `createQuery`.
|
|
265
|
-
|
|
266
|
-
```ts
|
|
267
|
-
// +page.ts
|
|
268
|
-
export const load = async ({ fetch }) => {
|
|
269
|
-
const initial = await fetchPeople({ pageIndex: 0, pageSize: 10 }, fetch)
|
|
270
|
-
return { initial }
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
```svelte
|
|
275
|
-
<script lang="ts">
|
|
276
|
-
let { data } = $props()
|
|
277
|
-
// pass data.initial into placeholderData on first render
|
|
278
|
-
</script>
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
## Common failure modes
|
|
282
|
-
|
|
283
|
-
- **Forgot `rowCount`.** Pager shows zero pages.
|
|
284
|
-
- **No `placeholderData: keepPreviousData`.** Empty-table flash on every page change.
|
|
285
|
-
- **Forgot `manualPagination: true`.** Table tries to paginate the already-paged window.
|
|
286
|
-
`getPageCount()` returns 1.
|
|
287
|
-
- **Driver state in `queryKey` is stale.** Always pass the current value, not a captured one.
|
|
288
|
-
- **No reset on filter change.** Stays on dead pages.
|
|
289
|
-
- **Plain `data: dataQuery.data?.rows`.** No reactivity — must be a getter.
|
|
290
|
-
- **Re-creating `createQuery` inside `$effect`.** It's a one-time call; create it at component init.
|
|
291
|
-
- **Reimplementing pagination math against `query.data` instead of calling
|
|
292
|
-
`table.nextPage()`.** Don't.
|
|
293
|
-
|
|
294
|
-
## Related skills
|
|
295
|
-
|
|
296
|
-
- `tanstack-table/svelte/client-to-server` — base server-side pattern (without Query).
|
|
297
|
-
- `tanstack-table/svelte/compose-with-tanstack-store` — atom-based driver state.
|
|
298
|
-
- `tanstack-table/svelte/compose-with-tanstack-pacer` — debounce / throttle high-frequency inputs.
|
|
299
|
-
- `tanstack-table/core/pagination` — manual mode semantics.
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: svelte/compose-with-tanstack-store
|
|
3
|
-
description: >
|
|
4
|
-
TanStack Table v9 is built on TanStack Store. Each state slice (sorting, pagination,
|
|
5
|
-
rowSelection, columnFilters, ...) is a separate atom. In Svelte, `@tanstack/svelte-store`
|
|
6
|
-
exposes `createAtom`, `useSelector`, `shallow`. Read `table.atoms.<slice>` per slice,
|
|
7
|
-
`table.store` flat, or `table.state` for the selector projection. Subscribe with
|
|
8
|
-
`subscribeTable(atom, selector?)` (returns `.current`). Own a slice externally with
|
|
9
|
-
`createAtom` + `atoms: { sorting: sortingAtom }`. Svelte 5+ only — `$state` / `$derived.by` /
|
|
10
|
-
`$effect.pre` reactivity.
|
|
11
|
-
type: composition
|
|
12
|
-
library: tanstack-table
|
|
13
|
-
framework: svelte
|
|
14
|
-
library_version: '9.0.0-alpha.48'
|
|
15
|
-
requires:
|
|
16
|
-
- state-management
|
|
17
|
-
sources:
|
|
18
|
-
- TanStack/table:docs/framework/svelte/guide/table-state.md
|
|
19
|
-
- TanStack/table:packages/svelte-table/src/reactivity.svelte.ts
|
|
20
|
-
- TanStack/table:packages/svelte-table/src/subscribe.ts
|
|
21
|
-
- TanStack/table:examples/svelte/basic-external-atoms/
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
# Compose with TanStack Store (Svelte)
|
|
25
|
-
|
|
26
|
-
`@tanstack/svelte-store` is the reactive primitive under `@tanstack/svelte-table` v9. The
|
|
27
|
-
table doesn't merely _use_ Store — its entire reactivity model is built from Store atoms with
|
|
28
|
-
rune backings.
|
|
29
|
-
|
|
30
|
-
## Mental model — three read surfaces
|
|
31
|
-
|
|
32
|
-
A registered v9 table exposes:
|
|
33
|
-
|
|
34
|
-
| Surface | Shape | When to use |
|
|
35
|
-
| --------------------- | --------------------------- | ------------------------------------------ |
|
|
36
|
-
| `table.atoms.<slice>` | `ReadonlyAtom<TSlice>` | Per-slice subscription / `.get()` snapshot |
|
|
37
|
-
| `table.store` | `ReadonlyStore<FlatState>` | Flat snapshot across registered slices |
|
|
38
|
-
| `table.state` | `TSelected` (from selector) | The selector projection (Svelte-only) |
|
|
39
|
-
|
|
40
|
-
Plus the writable internals:
|
|
41
|
-
|
|
42
|
-
- `table.baseAtoms.<slice>` — writable atom for state the table owns.
|
|
43
|
-
|
|
44
|
-
If a slice is supplied externally via `atoms`, `table.atoms.<slice>` reads from your atom and
|
|
45
|
-
`table.baseAtoms.<slice>` is unused for that slice.
|
|
46
|
-
|
|
47
|
-
## The Svelte bindings (what `svelteReactivity()` actually does)
|
|
48
|
-
|
|
49
|
-
The Svelte adapter ships `svelteReactivity()` and installs it as `coreReativityFeature`. It
|
|
50
|
-
maps Store primitives to runes:
|
|
51
|
-
|
|
52
|
-
- Readonly atoms → `$derived.by(fn)`
|
|
53
|
-
- Writable atoms → `$state(initialValue)`
|
|
54
|
-
- Subscriptions → `$effect.root` + `$effect`
|
|
55
|
-
- Batch → `flushSync`
|
|
56
|
-
|
|
57
|
-
This is why simple atom reads inside `.svelte` components (templates, `$derived`, `$effect`)
|
|
58
|
-
participate in reactivity automatically. There is no React-style `useStore` requirement.
|
|
59
|
-
|
|
60
|
-
## Pattern 1 — Read a slice without subscribing
|
|
61
|
-
|
|
62
|
-
For event handlers, async work, exports, anything outside of reactive markup. Cheap, no
|
|
63
|
-
subscription setup.
|
|
64
|
-
|
|
65
|
-
```ts
|
|
66
|
-
import type { SortingState } from '@tanstack/svelte-table'
|
|
67
|
-
|
|
68
|
-
function logSort() {
|
|
69
|
-
const sorting: SortingState = table.atoms.sorting.get()
|
|
70
|
-
console.log(sorting)
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`table.state` is the full snapshot equivalent.
|
|
75
|
-
|
|
76
|
-
## Pattern 2 — Reactive selector via `createTable`
|
|
77
|
-
|
|
78
|
-
The second argument to `createTable` is a TanStack Store selector. The result is exposed on
|
|
79
|
-
`table.state`. The default selector is `(state) => state`.
|
|
80
|
-
|
|
81
|
-
```svelte
|
|
82
|
-
<script lang="ts">
|
|
83
|
-
const table = createTable(
|
|
84
|
-
{
|
|
85
|
-
features,
|
|
86
|
-
rowModels: { paginatedRowModel: createPaginatedRowModel() },
|
|
87
|
-
columns,
|
|
88
|
-
get data() {
|
|
89
|
-
return data
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
(state) => ({
|
|
93
|
-
pageIndex: state.pagination.pageIndex,
|
|
94
|
-
pageSize: state.pagination.pageSize,
|
|
95
|
-
}),
|
|
96
|
-
)
|
|
97
|
-
</script>
|
|
98
|
-
|
|
99
|
-
<strong>Page {table.state.pageIndex + 1}</strong>
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The narrower the selector, the less your markup re-renders.
|
|
103
|
-
|
|
104
|
-
## Pattern 3 — Per-block subscription with `subscribeTable`
|
|
105
|
-
|
|
106
|
-
`subscribeTable(source, selector?)` is the dedicated per-component subscription. It uses
|
|
107
|
-
`shallow` compare and exposes a `.current` accessor.
|
|
108
|
-
|
|
109
|
-
```svelte
|
|
110
|
-
<script lang="ts">
|
|
111
|
-
import { subscribeTable } from '@tanstack/svelte-table'
|
|
112
|
-
|
|
113
|
-
// whole slice
|
|
114
|
-
const pagination = subscribeTable(table.atoms.pagination)
|
|
115
|
-
|
|
116
|
-
// narrowed
|
|
117
|
-
const pageSize = subscribeTable(table.atoms.pagination, (p) => p.pageSize)
|
|
118
|
-
|
|
119
|
-
// works on table.store too
|
|
120
|
-
const fullSnapshot = subscribeTable(table.store)
|
|
121
|
-
</script>
|
|
122
|
-
|
|
123
|
-
<span
|
|
124
|
-
>Page {pagination.current.pageIndex + 1} ({pageSize.current} per page)</span
|
|
125
|
-
>
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Inside per-row components, `subscribeTable(table.atoms.rowSelection, (s) => !!s[row.id])` keeps
|
|
129
|
-
that row's checkbox reactive without subscribing to the entire selection map.
|
|
130
|
-
|
|
131
|
-
## Pattern 4 — Own a slice externally with `createAtom`
|
|
132
|
-
|
|
133
|
-
When the app should own a slice — share across components, sync with URL, persist to storage —
|
|
134
|
-
create a stable atom and hand it to the table via `atoms`.
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
import { createAtom, useSelector } from '@tanstack/svelte-store'
|
|
138
|
-
import {
|
|
139
|
-
createTable,
|
|
140
|
-
rowPaginationFeature,
|
|
141
|
-
rowSortingFeature,
|
|
142
|
-
tableFeatures,
|
|
143
|
-
type PaginationState,
|
|
144
|
-
type SortingState,
|
|
145
|
-
} from '@tanstack/svelte-table'
|
|
146
|
-
|
|
147
|
-
const features = tableFeatures({
|
|
148
|
-
rowPaginationFeature,
|
|
149
|
-
rowSortingFeature,
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
const sortingAtom = createAtom<SortingState>([])
|
|
153
|
-
const paginationAtom = createAtom<PaginationState>({
|
|
154
|
-
pageIndex: 0,
|
|
155
|
-
pageSize: 10,
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
// Optional: a Svelte-reactive view onto each atom for use in markup.
|
|
159
|
-
const sorting = useSelector(sortingAtom)
|
|
160
|
-
const pagination = useSelector(paginationAtom)
|
|
161
|
-
|
|
162
|
-
const table = createTable({
|
|
163
|
-
features,
|
|
164
|
-
rowModels: {
|
|
165
|
-
/* ... */
|
|
166
|
-
},
|
|
167
|
-
columns,
|
|
168
|
-
get data() {
|
|
169
|
-
return data
|
|
170
|
-
},
|
|
171
|
-
atoms: {
|
|
172
|
-
sorting: sortingAtom,
|
|
173
|
-
pagination: paginationAtom,
|
|
174
|
-
},
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
// table.setPageIndex(2) writes through paginationAtom.
|
|
178
|
-
// paginationAtom.set(...) updates table.atoms.pagination immediately.
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Atom precedence: external `atoms.<slice>` wins over external `state.<slice>` which writes
|
|
182
|
-
into the internal `baseAtoms.<slice>`. **Never combine them on the same slice.**
|
|
183
|
-
|
|
184
|
-
## Pattern 5 — Cross-component / cross-module state
|
|
185
|
-
|
|
186
|
-
Because atoms are first-class subscribable values, you can read them outside the table component.
|
|
187
|
-
|
|
188
|
-
```ts
|
|
189
|
-
// stores/table-state.ts
|
|
190
|
-
import { createAtom } from '@tanstack/svelte-store'
|
|
191
|
-
import type { RowSelectionState } from '@tanstack/svelte-table'
|
|
192
|
-
|
|
193
|
-
export const rowSelectionAtom = createAtom<RowSelectionState>({})
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
```svelte
|
|
197
|
-
<!-- Toolbar.svelte -->
|
|
198
|
-
<script lang="ts">
|
|
199
|
-
import { useSelector } from '@tanstack/svelte-store'
|
|
200
|
-
import { rowSelectionAtom } from './stores/table-state'
|
|
201
|
-
|
|
202
|
-
const selection = useSelector(rowSelectionAtom)
|
|
203
|
-
const selectedCount = $derived(
|
|
204
|
-
Object.values(selection.current).filter(Boolean).length,
|
|
205
|
-
)
|
|
206
|
-
</script>
|
|
207
|
-
|
|
208
|
-
<button disabled={selectedCount === 0}>Delete {selectedCount}</button>
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
```svelte
|
|
212
|
-
<!-- TablePage.svelte -->
|
|
213
|
-
<script lang="ts">
|
|
214
|
-
import { rowSelectionAtom } from './stores/table-state'
|
|
215
|
-
|
|
216
|
-
const table = createTable({
|
|
217
|
-
features,
|
|
218
|
-
rowModels: {
|
|
219
|
-
/* ... */
|
|
220
|
-
},
|
|
221
|
-
columns,
|
|
222
|
-
get data() {
|
|
223
|
-
return data
|
|
224
|
-
},
|
|
225
|
-
atoms: { rowSelection: rowSelectionAtom },
|
|
226
|
-
})
|
|
227
|
-
</script>
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Pattern 6 — `useSelector` with custom equality
|
|
231
|
-
|
|
232
|
-
`useSelector(source, selector, { compare })` lets you switch comparison strategies — useful
|
|
233
|
-
for object selectors so you don't re-fire on every reference change.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import { shallow, useSelector } from '@tanstack/svelte-store'
|
|
237
|
-
|
|
238
|
-
const filterValues = useSelector(
|
|
239
|
-
table.atoms.columnFilters,
|
|
240
|
-
(filters) => Object.fromEntries(filters.map((f) => [f.id, f.value])),
|
|
241
|
-
{ compare: shallow },
|
|
242
|
-
)
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
`subscribeTable` already uses `shallow` by default, so prefer it for table sources unless you
|
|
246
|
-
need a custom compare.
|
|
247
|
-
|
|
248
|
-
## Pattern 7 — Direct base-atom writes (last resort)
|
|
249
|
-
|
|
250
|
-
When a slice is internally owned and you really need to write outside a feature API:
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
Do not do this for externally-owned slices — write to your external atom instead. The base
|
|
257
|
-
atom is dormant in that case and your write will be silently ignored next sync.
|
|
258
|
-
|
|
259
|
-
## Common failure modes
|
|
260
|
-
|
|
261
|
-
- **Reading a slice that wasn't registered.** `table.atoms.rowSelection` is `undefined` if
|
|
262
|
-
`rowSelectionFeature` isn't in `features`. TS will catch it if you used `tableFeatures()`.
|
|
263
|
-
- **Creating atoms inside reactive blocks.** Atoms must be stable. Module scope or top-level
|
|
264
|
-
component scope, never inside `$derived` / `$effect`.
|
|
265
|
-
- **`useSelector` without `.current`.** `selection.pageIndex` is wrong — `selection.current.pageIndex`.
|
|
266
|
-
- **Mixing `atoms.X` and `state.X`.** Atom wins, callback never fires.
|
|
267
|
-
- **`tableState` as a plain object.** No reactivity. Use `subscribeTable`, `useSelector`, or
|
|
268
|
-
the `createTable` selector.
|
|
269
|
-
- **Reimplementing `useSelector` with `$effect`.** Built-in is more efficient and uses
|
|
270
|
-
shallow compare.
|
|
271
|
-
|
|
272
|
-
## Related skills
|
|
273
|
-
|
|
274
|
-
- `tanstack-table/svelte/table-state` — full reactivity model and selector patterns.
|
|
275
|
-
- `tanstack-table/core/state-management` — atom precedence rules.
|
|
276
|
-
- `tanstack-table/svelte/client-to-server` — atoms as the data-driver for server queries.
|
|
277
|
-
- `tanstack-table/svelte/production-readiness` — selector / subscription tuning.
|