@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.
@@ -0,0 +1,186 @@
1
+ ---
2
+ name: preact/compose-with-tanstack-pacer
3
+ description: >
4
+ Use `@tanstack/preact-pacer` to debounce / throttle the high-frequency writes
5
+ that drive an interactive table: column filter inputs and column resize
6
+ state. Pattern: import `useDebouncedCallback` from
7
+ `@tanstack/preact-pacer/debouncer` (or `useThrottledCallback` for resize),
8
+ wrap your `column.setFilterValue` / `table.setColumnSizing` calls, and let
9
+ the table's expensive row-model recompute happen on the trailing edge.
10
+ Routing keywords: preact-pacer, debounce filter, throttle resize, useDebouncedCallback,
11
+ performant filtering.
12
+ type: composition
13
+ library: tanstack-table
14
+ framework: preact
15
+ library_version: '9.0.0-alpha.47'
16
+ requires:
17
+ - filtering
18
+ - column-layout
19
+ sources:
20
+ - TanStack/table:examples/preact/filters/
21
+ - TanStack/table:examples/preact/column-resizing-performant/
22
+ - TanStack/table:examples/react/with-tanstack-form/
23
+ ---
24
+
25
+ Filtering and column resizing fire a lot of events. Each `column.setFilterValue(...)` invalidates `filteredRowModel`, then `paginatedRowModel`, then re-renders subscribed components. For large tables that is the difference between a snappy UI and a janky one. Debounce or throttle the writes with @tanstack/preact-pacer.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install @tanstack/preact-pacer
31
+ ```
32
+
33
+ ## Pattern 1 — Debounced column filter input
34
+
35
+ Render the input value from local state (immediate visual feedback) and push the value into the table on a debounce.
36
+
37
+ ```tsx
38
+ import { useState } from 'preact/hooks'
39
+ import { useDebouncedCallback } from '@tanstack/preact-pacer/debouncer'
40
+ import type { Column } from '@tanstack/preact-table'
41
+
42
+ function FilterInput<TFeatures, TData>({
43
+ column,
44
+ }: {
45
+ column: Column<TFeatures, TData>
46
+ }) {
47
+ const initial = (column.getFilterValue() as string | undefined) ?? ''
48
+ const [value, setValue] = useState(initial)
49
+
50
+ const pushToTable = useDebouncedCallback(
51
+ (next: string) => column.setFilterValue(next),
52
+ { wait: 250 },
53
+ )
54
+
55
+ return (
56
+ <input
57
+ type="text"
58
+ value={value}
59
+ onInput={(e) => {
60
+ const next = (e.target as HTMLInputElement).value
61
+ setValue(next)
62
+ pushToTable(next)
63
+ }}
64
+ placeholder="Search…"
65
+ />
66
+ )
67
+ }
68
+ ```
69
+
70
+ The local `value` keeps the cursor and typing responsive; `pushToTable` only runs on the trailing edge, so the table only recomputes the filtered row model once per pause.
71
+
72
+ Same pattern works for the global filter via `table.setGlobalFilter`.
73
+
74
+ Source: `examples/preact/filters/`.
75
+
76
+ ## Pattern 2 — Throttled column resize
77
+
78
+ Column resize fires per-mousemove. Throttling keeps the resize visually smooth without re-running the full layout on every pixel.
79
+
80
+ ```tsx
81
+ import { useThrottledCallback } from '@tanstack/preact-pacer/throttler'
82
+
83
+ function ResizeHandle({ header, table }) {
84
+ const pushSize = useThrottledCallback(
85
+ (size: number) => {
86
+ table.setColumnSizing((prev) => ({ ...prev, [header.column.id]: size }))
87
+ },
88
+ { wait: 16 }, // ~60fps
89
+ )
90
+
91
+ // Hook this into your existing pointermove handler.
92
+ return null
93
+ }
94
+ ```
95
+
96
+ In v9, column sizing is driven by `columnSizingFeature` + `table.setColumnSizing`. The throttle wraps the write side; the read side stays direct.
97
+
98
+ Source: `examples/preact/column-resizing-performant/`.
99
+
100
+ ## When NOT to debounce
101
+
102
+ - Sorting (`table.setSorting`) — fires once per click.
103
+ - Pagination (`table.setPageIndex`, `table.setPageSize`) — fires once per page.
104
+ - Row selection (`row.toggleSelected`) — fires once per toggle.
105
+
106
+ Debouncing these adds latency for no win. Reach for pacer only on input/resize/scroll-driven writes.
107
+
108
+ ## With External Atoms
109
+
110
+ If you've moved a slice to an external atom, debounce the atom write instead of the table API.
111
+
112
+ ```tsx
113
+ const globalFilterAtom = useCreateAtom<string>('')
114
+
115
+ const pushFilter = useDebouncedCallback(
116
+ (next: string) => globalFilterAtom.set(next),
117
+ { wait: 250 },
118
+ )
119
+ ```
120
+
121
+ The table reads from the atom; the atom now changes less often; the row model recomputes less often.
122
+
123
+ ## Common Mistakes
124
+
125
+ ### CRITICAL Wrapping `column.setFilterValue` AND reading filter via `column.getFilterValue()` in the same input
126
+
127
+ Wrong:
128
+
129
+ ```tsx
130
+ const debouncedSet = useDebouncedCallback(
131
+ (v: string) => column.setFilterValue(v),
132
+ { wait: 250 },
133
+ )
134
+ return (
135
+ <input
136
+ value={(column.getFilterValue() ?? '') as string} // doesn't reflect typed value
137
+ onInput={(e) => debouncedSet((e.target as HTMLInputElement).value)}
138
+ />
139
+ )
140
+ ```
141
+
142
+ Correct: hold local state for the visual `value`, debounce the write.
143
+
144
+ ```tsx
145
+ const [v, setV] = useState((column.getFilterValue() ?? '') as string)
146
+ const debouncedSet = useDebouncedCallback(
147
+ (next: string) => column.setFilterValue(next),
148
+ { wait: 250 },
149
+ )
150
+
151
+ return (
152
+ <input
153
+ value={v}
154
+ onInput={(e) => {
155
+ const next = (e.target as HTMLInputElement).value
156
+ setV(next)
157
+ debouncedSet(next)
158
+ }}
159
+ />
160
+ )
161
+ ```
162
+
163
+ Otherwise the input lags by the debounce wait — the user sees stale characters.
164
+
165
+ ### HIGH Debouncing the wrong direction
166
+
167
+ Wrong: debouncing the read (`column.getFilterValue`), which is just a memoized derivation.
168
+
169
+ Correct: debounce the **write** (`column.setFilterValue`) — that's what triggers the row-model recompute.
170
+
171
+ ### HIGH Treating pacer as a substitute for stable references
172
+
173
+ Wrong: debouncing every `useTable` call.
174
+
175
+ Correct: pacer doesn't fix unstable `_features` / `columns` / `data` references. Stabilize those first; reach for pacer for input/scroll/resize hotspots.
176
+ Source: `docs/framework/preact/guide/table-state.md` (FAQ #1).
177
+
178
+ ### MEDIUM Forgetting that debounce delays the trailing edge
179
+
180
+ The first keystroke still hits the table at the trailing edge of the debounce window. If you want a leading-edge call (e.g. fire immediately, then debounce subsequent calls), use the `{ leading: true, trailing: true }` shape.
181
+
182
+ ## See Also
183
+
184
+ - `tanstack-table/filtering` — column / global filter state shape.
185
+ - `tanstack-table/column-layout` — column sizing / pinning / visibility.
186
+ - `tanstack-table/preact/production-readiness` — narrow selectors, stable refs.
@@ -0,0 +1,283 @@
1
+ ---
2
+ name: preact/compose-with-tanstack-query
3
+ description: >
4
+ Server-side / async data flow with `@tanstack/preact-query`. Key the query on
5
+ the table state that drives the request (pagination + sort + filters), pass
6
+ `placeholderData: keepPreviousData` to avoid a "0 rows flash" between pages,
7
+ set `manualPagination` / `manualSorting` / `manualFiltering` for the slices
8
+ the server owns, supply `rowCount`, and let `table.set*` writes to external
9
+ atoms re-key the query. Routing keywords: preact-query, server pagination,
10
+ keepPreviousData, useQuery, manualPagination, rowCount, fetchData.
11
+ type: composition
12
+ library: tanstack-table
13
+ framework: preact
14
+ library_version: '9.0.0-alpha.47'
15
+ requires:
16
+ - preact/client-to-server
17
+ - pagination
18
+ - state-management
19
+ sources:
20
+ - TanStack/table:examples/preact/with-tanstack-query/src/main.tsx
21
+ - TanStack/table:examples/preact/with-tanstack-query/src/fetchData.ts
22
+ - TanStack/table:docs/framework/preact/guide/table-state.md
23
+ ---
24
+
25
+ This skill is the @tanstack/preact-query recipe for server-side tables. Read `tanstack-table/preact/client-to-server` first for the manual-mode mechanics; this skill is the Preact Query-specific wiring on top.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install @tanstack/preact-query @tanstack/preact-table @tanstack/preact-store
31
+ ```
32
+
33
+ ## The Standard Recipe
34
+
35
+ Own the slices that drive the request with external atoms. Read them with `useSelector` so the `queryKey` is reactive. Pass them through `options.atoms`. Set `manual*` for the slices the server owns. Use `placeholderData: keepPreviousData` so pagination doesn't flash empty.
36
+
37
+ ```tsx
38
+ import { useMemo, useReducer } from 'preact/hooks'
39
+ import { render } from 'preact'
40
+ import {
41
+ QueryClient,
42
+ QueryClientProvider,
43
+ keepPreviousData,
44
+ useQuery,
45
+ } from '@tanstack/preact-query'
46
+ import { useCreateAtom, useSelector } from '@tanstack/preact-store'
47
+ import {
48
+ createColumnHelper,
49
+ rowPaginationFeature,
50
+ tableFeatures,
51
+ useTable,
52
+ type PaginationState,
53
+ } from '@tanstack/preact-table'
54
+ import { fetchData } from './fetchData'
55
+ import type { Person } from './fetchData'
56
+
57
+ const queryClient = new QueryClient()
58
+ const _features = tableFeatures({ rowPaginationFeature })
59
+ const columnHelper = createColumnHelper<typeof _features, Person>()
60
+
61
+ const columns = columnHelper.columns([
62
+ columnHelper.accessor('firstName', {
63
+ header: 'First Name',
64
+ cell: (info) => info.getValue(),
65
+ }),
66
+ columnHelper.accessor('lastName', { header: 'Last Name' }),
67
+ columnHelper.accessor('age', { header: 'Age' }),
68
+ ])
69
+
70
+ function App() {
71
+ const paginationAtom = useCreateAtom<PaginationState>({
72
+ pageIndex: 0,
73
+ pageSize: 10,
74
+ })
75
+ const pagination = useSelector(paginationAtom)
76
+
77
+ const dataQuery = useQuery({
78
+ queryKey: ['data', pagination],
79
+ queryFn: () => fetchData(pagination),
80
+ placeholderData: keepPreviousData, // no "0 rows" flash between pages
81
+ })
82
+
83
+ const defaultData = useMemo(() => [], [])
84
+
85
+ const table = useTable(
86
+ {
87
+ _features,
88
+ _rowModels: {}, // server owns slicing
89
+ columns,
90
+ data: dataQuery.data?.rows ?? defaultData,
91
+ rowCount: dataQuery.data?.rowCount,
92
+ atoms: { pagination: paginationAtom },
93
+ manualPagination: true,
94
+ },
95
+ (state) => state,
96
+ )
97
+
98
+ // table.nextPage() writes to paginationAtom → queryKey changes → refetch.
99
+ return null
100
+ }
101
+
102
+ render(
103
+ <QueryClientProvider client={queryClient}>
104
+ <App />
105
+ </QueryClientProvider>,
106
+ document.getElementById('root')!,
107
+ )
108
+ ```
109
+
110
+ Source: `examples/preact/with-tanstack-query/src/main.tsx`.
111
+
112
+ ## Server `fetchData` Shape
113
+
114
+ The fetcher returns the page of rows plus the total row count so `table.getPageCount()` is correct.
115
+
116
+ ```ts
117
+ // fetchData.ts
118
+ import type { PaginationState } from '@tanstack/preact-table'
119
+
120
+ export type Person = {
121
+ firstName: string
122
+ lastName: string
123
+ age: number /* … */
124
+ }
125
+
126
+ export async function fetchData(pagination: PaginationState): Promise<{
127
+ rows: Person[]
128
+ rowCount: number
129
+ }> {
130
+ // server returns the current page and the total count
131
+ const res = await fetch(
132
+ `/api/people?page=${pagination.pageIndex}&size=${pagination.pageSize}`,
133
+ )
134
+ return res.json()
135
+ }
136
+ ```
137
+
138
+ Source: `examples/preact/with-tanstack-query/src/fetchData.ts`.
139
+
140
+ ## Adding Sorting and Filters
141
+
142
+ Add more external atoms; include them in the `queryKey`; set the matching `manual*` flag. The server's fetcher accepts whatever shape you forward.
143
+
144
+ ```tsx
145
+ const _features = tableFeatures({
146
+ rowPaginationFeature,
147
+ rowSortingFeature,
148
+ columnFilteringFeature,
149
+ globalFilteringFeature,
150
+ })
151
+
152
+ const paginationAtom = useCreateAtom<PaginationState>({
153
+ pageIndex: 0,
154
+ pageSize: 10,
155
+ })
156
+ const sortingAtom = useCreateAtom<SortingState>([])
157
+ const columnFiltersAtom = useCreateAtom<ColumnFiltersState>([])
158
+ const globalFilterAtom = useCreateAtom<string>('')
159
+
160
+ const pagination = useSelector(paginationAtom)
161
+ const sorting = useSelector(sortingAtom)
162
+ const columnFilters = useSelector(columnFiltersAtom)
163
+ const globalFilter = useSelector(globalFilterAtom)
164
+
165
+ const dataQuery = useQuery({
166
+ queryKey: ['data', pagination, sorting, columnFilters, globalFilter],
167
+ queryFn: () =>
168
+ fetchData({ pagination, sorting, columnFilters, globalFilter }),
169
+ placeholderData: keepPreviousData,
170
+ })
171
+
172
+ const table = useTable({
173
+ _features,
174
+ _rowModels: {},
175
+ columns,
176
+ data: dataQuery.data?.rows ?? defaultData,
177
+ rowCount: dataQuery.data?.rowCount,
178
+ atoms: {
179
+ pagination: paginationAtom,
180
+ sorting: sortingAtom,
181
+ columnFilters: columnFiltersAtom,
182
+ globalFilter: globalFilterAtom,
183
+ },
184
+ manualPagination: true,
185
+ manualSorting: true,
186
+ manualFiltering: true,
187
+ })
188
+ ```
189
+
190
+ ## Common Mistakes
191
+
192
+ ### CRITICAL `manualPagination` without `rowCount`
193
+
194
+ Wrong:
195
+
196
+ ```tsx
197
+ useTable({
198
+ /* … */,
199
+ data: dataQuery.data?.rows ?? defaultData,
200
+ manualPagination: true,
201
+ atoms: { pagination: paginationAtom },
202
+ // no rowCount
203
+ })
204
+ table.getPageCount() // Infinity
205
+ ```
206
+
207
+ Correct: always pass `rowCount: dataQuery.data?.rowCount`.
208
+ Source: `examples/preact/with-tanstack-query/src/main.tsx`.
209
+
210
+ ### CRITICAL `queryKey` that doesn't include reactive table state
211
+
212
+ Wrong:
213
+
214
+ ```tsx
215
+ useQuery({
216
+ queryKey: ['data'],
217
+ queryFn: () => fetchData(pagination),
218
+ })
219
+ ```
220
+
221
+ Correct:
222
+
223
+ ```tsx
224
+ useQuery({
225
+ queryKey: ['data', pagination /* + sorting, filters, etc. */],
226
+ queryFn: () => fetchData(pagination),
227
+ })
228
+ ```
229
+
230
+ The query cache must vary by the slice values, or you'll fetch once and never refresh on user interaction.
231
+ Source: `examples/preact/with-tanstack-query/src/main.tsx`.
232
+
233
+ ### HIGH Missing `placeholderData: keepPreviousData`
234
+
235
+ Wrong: data goes `undefined` between pages; the table flashes empty.
236
+
237
+ Correct: include `placeholderData: keepPreviousData` so the table keeps the last page rendered until the new page resolves.
238
+ Source: `examples/preact/with-tanstack-query/src/main.tsx`.
239
+
240
+ ### HIGH Inline `data: dataQuery.data?.rows ?? []`
241
+
242
+ Wrong:
243
+
244
+ ```tsx
245
+ useTable({ /* … */, data: dataQuery.data?.rows ?? [] }) // new [] every render
246
+ ```
247
+
248
+ Correct:
249
+
250
+ ```tsx
251
+ const defaultData = useMemo(() => [], [])
252
+ useTable({ /* … */, data: dataQuery.data?.rows ?? defaultData })
253
+ ```
254
+
255
+ A new empty array each render busts row-model memos.
256
+
257
+ ### HIGH Keeping the client-side `_rowModels` when manual
258
+
259
+ Wrong:
260
+
261
+ ```tsx
262
+ useTable({
263
+ _features,
264
+ _rowModels: { paginatedRowModel: createPaginatedRowModel() }, // wasted work
265
+ /* … */,
266
+ manualPagination: true,
267
+ })
268
+ ```
269
+
270
+ Correct: drop the row-model factory whose stage the server owns. With `manualPagination: true`, the server returns the page slice already.
271
+
272
+ ### MEDIUM Creating a new `paginationAtom` per render
273
+
274
+ Wrong: `createAtom(...)` inside the component body.
275
+
276
+ Correct: `useCreateAtom(...)` (or atom at module scope).
277
+ Source: `examples/preact/basic-external-atoms/src/main.tsx`.
278
+
279
+ ## See Also
280
+
281
+ - `tanstack-table/preact/client-to-server` — manual-mode mechanics independent of any specific async lib.
282
+ - `tanstack-table/preact/compose-with-tanstack-store` — slice atoms and sharing state.
283
+ - `tanstack-table/preact/production-readiness` — narrow selectors, stable refs.