@tanstack/svelte-table 9.0.0-alpha.9 → 9.0.0-beta.10

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