-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Expand file tree
/
Copy pathdomain_map.yaml
More file actions
7110 lines (6968 loc) · 372 KB
/
Copy pathdomain_map.yaml
File metadata and controls
7110 lines (6968 loc) · 372 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
library:
name: '@tanstack/table'
version: 9.0.0-alpha.47
repository: https://github.com/TanStack/table
homepage: https://tanstack.com/table
description:
'Headless data-grid library: features, state, and APIs for building powerful, type-safe
tables and datagrids while keeping full control of markup, styles, and behavior. Framework-agnostic
core (`@tanstack/table-core`) with adapters for React, Vue, Solid, Svelte, Angular, Lit, and Preact.'
primary_framework: framework-agnostic
monorepo: true
meta:
generated_by: '@tanstack/intent scaffold (Phase 3 deep-read merge)'
date: '2026-05-17'
status: reviewed
maintainer_review_pending: false
phase_4_date: '2026-05-17'
domains:
- name: Core foundations
slug: core-foundations
description:
Setup, column definitions, state management, and customization of built-in feature behavior.
State-management is the prerequisite for every other skill.
- name: Row-model features
slug: row-model-features
description:
Features driven by entries in `_rowModels` — filtering (column+global+faceting+fuzzy),
sorting, pagination, grouping, expanding. All have `manual<Feature>` opt-outs for server-side data.
- name: UI-state features
slug: ui-state-features
description:
Features that are pure UI state — no row model needed. Column layout (visibility/ordering/pinning/sizing/resizing),
row pinning, row selection.
- name: Framework adapters
slug: framework-adapters
description:
Per-framework reactivity bindings and rendering integration. Each adapter has its own `table-state`
skill; React adds Subscribe-for-React-Compiler, Angular adds structural directives, Lit adds the TableController
pattern.
- name: Lifecycle
slug: lifecycle
description:
'End-to-end journeys that cross-cut features: getting-started, v8→v9 migration, client-to-server
conversion, production-readiness.'
- name: Composition
slug: composition
description:
Patterns for using TanStack Table with sibling TanStack libraries (Store, Query, Virtual,
Form, Pacer, Devtools). Per maintainer decision, no per-UI-library composition skills.
skills:
- name: Setup
slug: setup
domain: core-foundations
description:
Install a table adapter and wire up a first working table with `_features`, `_rowModels`,
columns, and data.
type: core
packages:
- '@tanstack/table-core'
covers:
- tableFeatures
- _features
- _rowModels
- useTable
- constructTable
- _createTable
- stockFeatures
- coreFeatures
- storeReactivityBindings
- FlexRender
- flexRender
- table.getHeaderGroups
- table.getRowModel
tasks:
- Render a first read-only table from an array of objects
- Decide between an empty `tableFeatures({})` core-only setup vs `stockFeatures` (all features) vs hand-picked
feature imports
- Wire up the matching adapter (`useTable` / `injectTable` / `createTable` / `constructTable`) and render
markup with `<table.FlexRender />` or `flexRender`
- Use `@tanstack/table-core` + `storeReactivityBindings()` directly (vanilla JS) when no framework adapter
exists
failure_modes:
- mistake: Omits `_features` and `_rowModels`
mechanism:
v9 requires `_features` (and an `_rowModels` map, even if empty) at the top of `useTable`/`constructTable`
options. Without `_features`, TypeScript loses feature-state inference and runtime construction
has no feature plugins to register.
wrong_pattern: |
// v8-flavoured, breaks in v9
const table = useTable({
columns,
data,
getCoreRowModel: getCoreRowModel(), // v8 option name
})
correct_pattern: |
// v9 minimal table
const _features = tableFeatures({}) // empty = core features only
const table = useTable({
_features,
_rowModels: {}, // core row model auto-included
columns,
data,
})
source: examples/react/basic-use-table/src/main.tsx:56-109; docs/guide/tables.md:33-47; docs/framework/react/guide/migrating.md:78-103
priority: CRITICAL
status: active
version_context:
v9 alpha; affects every user upgrading from v8 — the hook is renamed (`useReactTable`
→ `useTable`) AND new required options were introduced
- mistake: Calls `tableFeatures({})` inside the component body
mechanism:
A fresh `_features` object on each render destroys the table's stable reference for feature
registration, the same way unstable `columns`/`data` cause infinite re-renders. `_features` must
be hoisted to module scope or memoized.
wrong_pattern: |
function MyTable() {
// ❌ new object every render -> potential re-construct + state churn
const _features = tableFeatures({ rowSortingFeature })
const table = useTable({ _features, _rowModels: {}, columns, data })
}
correct_pattern: |
// ✅ module-scoped, stable reference
const _features = tableFeatures({ rowSortingFeature })
function MyTable() {
const table = useTable({ _features, _rowModels: {}, columns, data })
}
source:
docs/guide/data.md:184-244; examples/react/basic-use-table/src/main.tsx:56 (defined outside
component)
priority: HIGH
status: active
version_context: v9 alpha; new in v9 because v8 had no `_features` option
skills:
- state-management
- mistake: Reaches for `stockFeatures` by default
mechanism:
Reaching for `stockFeatures` re-introduces v8 bundle size (~15–20kb), silently negating
v9's opt-in tree-shaking. The v9 default is hand-picked features; `stockFeatures` exists only as
a v8 escape hatch.
wrong_pattern: |
// ❌ ships every feature, even unused ones
import { useTable, stockFeatures } from '@tanstack/react-table'
const table = useTable({
_features: stockFeatures,
_rowModels: {},
columns,
data,
})
correct_pattern: |
// ✅ only the features you use
import {
tableFeatures,
useTable,
rowSortingFeature,
rowPaginationFeature,
} from '@tanstack/react-table'
const _features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
})
source: docs/framework/react/guide/migrating.md:9-12,136-147; packages/table-core/src/features/stockFeatures.ts:38-53
priority: MEDIUM
status: active
version_context: v9 alpha; `stockFeatures` is documented but discouraged for new code
- mistake: Adds a row model without registering its feature
mechanism:
Row model factories like `createSortedRowModel` only run if the matching feature (e.g.
`rowSortingFeature`) is also registered in `_features`. TypeScript flags this when state slices/APIs
are accessed, but runtime silently degrades — `table.atoms.sorting` is undefined and sort handlers
do nothing.
wrong_pattern: |
// ❌ rowSortingFeature missing from _features — sortedRowModel orphaned
const _features = tableFeatures({ rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns), // no-op
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
correct_pattern: |
// ✅ feature + row model registered together
const _features = tableFeatures({
rowSortingFeature,
rowPaginationFeature,
})
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns,
data,
})
source: docs/guide/row-models.md:46-78; examples/react/basic-external-atoms/src/main.tsx:27-30,81-92
priority: HIGH
status: active
version_context: v9 alpha; new failure surface since v8 had no _features/feature-row-model pairing
- mistake: Passing empty array literal to data causes infinite rerenders
mechanism: |
`const data = items ?? []` creates a new `[]` reference on every render. The
table sees a fresh `data` prop and rebuilds row models; if any callback in the
same render also reads from the table, you get an infinite re-render loop.
wrong_pattern: |
// ❌ Fresh [] each render — infinite loop when items is undefined
const table = useReactTable({
data: items ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
})
correct_pattern: |
// ✅ Hoist the empty fallback OR memoize
const EMPTY: MyRow[] = []
const table = useReactTable({
data: items ?? EMPTY,
columns,
getCoreRowModel: getCoreRowModel(),
})
// OR
const data = useMemo(() => items ?? [], [items])
source:
https://github.com/TanStack/table/issues/4566 (Empty data causes infinite looping), https://github.com/TanStack/table/issues/6002
(if data is empty array, then rendering table is causing infinite rerenders)
priority: CRITICAL
status: active
version_context:
Affects every version. Top recurring beginner issue. Maintainer (KevinVandy) explicitly
says docs need to make stability obvious.
skills:
- setup
- getting-started
- state-management
- mistake: Defining columns inside the render body without useMemo
mechanism: |
Defining `columns` inline inside the component body creates a fresh column array
every render. Table internals see new column references and rebuild header
groups, recalculate sizes, and lose memo caches — leading to slow renders and
cells/components remounting on every parent state change.
wrong_pattern: |
// ❌ Columns recreated every render
function MyTable({ data }) {
const columns = [
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('email', { header: 'Email' }),
]
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
...
}
correct_pattern: |
// ✅ Hoist outside, or useMemo with stable deps
const columns = [
columnHelper.accessor('name', { header: 'Name' }),
columnHelper.accessor('email', { header: 'Email' }),
]
function MyTable({ data }) {
const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() })
...
}
source:
https://github.com/TanStack/table/issues/5141 (What props passed to useReactTable hook have
to be stable?), https://github.com/TanStack/table/issues/4794 (unnecessary rerenders)
priority: CRITICAL
status: active
version_context:
'Top-priority pattern across v7, v8, v9. v9 makes columns/data readonly (PR #6183)
to nudge users.'
skills:
- setup
- state-management
- production-readiness
- mistake: Hallucinating react-table v7 / pre-v9 @tanstack/[framework]-table APIs
mechanism:
Every major release of TanStack Table (formerly react-table) has been a substantial upgrade.
Agents trained on older data confidently produce v7 or v8 API shapes that no longer exist in v9
(e.g. `useReactTable`, inline `getCoreRowModel()` as an option, `useTable` from react-table v7).
wrong_pattern: |-
// ❌ v7 / v8 patterns the agent invents
import { useTable, useSortBy } from 'react-table' // v7
const table = useTable({ columns, data }, useSortBy)
// or v8
import { useReactTable, getCoreRowModel } from '@tanstack/react-table'
const table = useReactTable({ columns, data, getCoreRowModel: getCoreRowModel() })
correct_pattern: |-
// ✅ v9 pattern — `_features` + `_rowModels`
import { useTable, tableFeatures, rowSortingFeature, createSortedRowModel, sortFns } from '@tanstack/react-table'
const _features = tableFeatures({ rowSortingFeature })
const table = useTable({
_features,
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
})
priority: CRITICAL
version_context:
v7→v8 and v8→v9 both shifted the API substantially; agents trained on any pre-v9
data will produce wrong shapes.
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- mistake: Reimplementing what TanStack Table's built-in APIs already provide
mechanism:
TanStack Table IS a state-management coordinator with built-in APIs for nearly every state
transition (`table.setSorting`, `row.toggleSelected`, `table.nextPage`, `table.setColumnFilters`,
`column.toggleVisibility`, …). Agents often write their own setState logic, click handlers, or sort/filter
loops rather than using the built-ins, producing more code that is also less correct (skips internal
invariants, breaks reset APIs).
wrong_pattern: |-
// ❌ Reimplements sorting state manually instead of using the API
const [sorting, setSorting] = useState([])
const sortedData = useMemo(() => [...data].sort((a,b) => /* …custom… */), [data, sorting])
// then uses sortedData directly, bypassing the table
correct_pattern: |-
// ✅ Use the built-in APIs — table handles state, reset, multi-sort, etc.
const table = useTable({
_features: tableFeatures({ rowSortingFeature }),
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
})
// then: table.setSorting(...), column.toggleSorting(), header.getToggleSortingHandler()
priority: CRITICAL
version_context:
'Always present. The maintainer flags this as the #1 tell that "an AI wrote this."
See `setSorting`/`setColumnFilters`/`toggleSelected`/`nextPage`/etc.'
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- mistake: API or state slice "missing" because the feature was not registered in `_features`
mechanism:
In v9, `_features` is a tree-shakeable registry. If a feature is not in `_features`, TypeScript
hides its APIs and the runtime atom is not created. Agents who copy a snippet for `table.setColumnFilters(...)`
without registering `columnFilteringFeature` see a TS error or `table.atoms.columnFilters` is undefined
— and may incorrectly conclude the feature is broken or removed in v9.
wrong_pattern: |-
// ❌ rowSortingFeature missing — table.setSorting / state.sorting unavailable
const _features = tableFeatures({}) // empty
const table = useTable({ _features, _rowModels: {}, columns, data })
table.setSorting([{ id: 'age', desc: true }]) // ❌ does not exist on this table type
correct_pattern: |-
// ✅ Register every feature you intend to use; pair with its row model when applicable
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns, data,
})
priority: CRITICAL
version_context:
v9-specific. This is the first version of TanStack Table where features must be declared
for TypeScript and runtime to expose them. Expect heavy confusion from devs and agents trained on
v8.
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- mistake: Bundling stockFeatures or all features when only a few are used
mechanism:
TanStack Table v9 is tree-shakeable specifically so you only pay for features you use.
Registering `stockFeatures` (or every feature "just in case") forfeits the bundle benefit that motivated
the v9 redesign.
wrong_pattern: |-
// ❌ Pulls in every feature even though only sorting+pagination are used
import { stockFeatures, tableFeatures } from '@tanstack/react-table'
const _features = tableFeatures(stockFeatures)
correct_pattern: |-
// ✅ Register only what this table uses
import { tableFeatures, rowSortingFeature, rowPaginationFeature } from '@tanstack/react-table'
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
priority: HIGH
version_context: v9. Tree-shaking via `_features` is one of the headline reasons for the rewrite.
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- name: Column Definitions
slug: column-definitions
domain: core-foundations
description:
Define columns with `createColumnHelper<typeof _features, TData>()` to extract data and
render headers/cells/footers.
type: core
packages:
- '@tanstack/table-core'
covers:
- createColumnHelper
- columnHelper.accessor
- columnHelper.display
- columnHelper.group
- columnHelper.columns
- ColumnDef
- AccessorKeyColumnDef
- AccessorFnColumnDef
- DisplayColumnDef
- GroupColumnDef
- accessorKey
- accessorFn
- header
- cell
- footer
- aggregatedCell
- id
- getRowId
- DeepKeys
- flexRender
tasks:
- Create a typed `columnHelper` and define accessor/display/group columns with strong inference
- 'Pick between `accessorKey: "name.first"` (deep key with dots) and `accessorFn: row => row.name.first`
for nested / computed values'
- Use `getRowId` to derive stable row identifiers from data (instead of array index) so selection/expansion
survive data updates
- Render headers/cells/footers with `<table.FlexRender />` (or `flexRender`) so string / JSX / function
forms all work
failure_modes:
- mistake: Passes only TData to `createColumnHelper`
mechanism:
'v9 changed the generic order: `createColumnHelper<TFeatures, TData>`. v8 code passed only
`<TData>`, which now binds `TData` into the `TFeatures` slot and breaks every column type. The compiler
error is noisy and not obvious.'
wrong_pattern: |
// ❌ v8 signature — TData ends up in the TFeatures slot
const columnHelper = createColumnHelper<Person>()
correct_pattern: |
// ✅ v9 — TFeatures first, TData second; use typeof _features
const _features = tableFeatures({ rowSortingFeature })
const columnHelper = createColumnHelper<typeof _features, Person>()
source:
packages/table-core/src/helpers/columnHelper.ts:99-103; docs/framework/react/guide/migrating.md:484-499;
examples/react/basic-external-atoms/src/main.tsx:32
priority: CRITICAL
status: active
version_context: v9 alpha; breaking change vs v8 generic signature
- mistake: Accessor function returns an object/array
mechanism:
The accessed value is what the table uses for sorting, filtering, faceting, and grouping.
Returning a non-primitive value means the built-in sort/filter/aggregation functions silently misbehave
or throw — only `displayCell` ever sees the value formatted. A primitive `string`/`number`/`Date`
is expected unless you supply a matching `sortFn`/`filterFn`/`aggregationFn`.
wrong_pattern: |
// ❌ returns an object — built-in alphanumeric/text sort and includesString filter break
columnHelper.accessor((row) => row.name, {
id: 'name',
cell: (info) => `${info.getValue().first} ${info.getValue().last}`,
})
correct_pattern: |
// ✅ accessor returns a primitive; cell can still format it
columnHelper.accessor((row) => `${row.name.first} ${row.name.last}`, {
id: 'fullName',
cell: (info) => info.getValue(),
})
source: docs/guide/column-defs.md:231; examples/react/basic-subscribe/src/main.tsx:103-108
priority: HIGH
status: active
version_context: always present
skills:
- customizing-feature-behavior
- mistake: Omits `id` on an accessorFn column
mechanism:
When a column uses `accessorFn` (not `accessorKey`), there is nothing to derive an id from.
The constructor throws "coreColumnsFeature require an id when using an accessorFn" in development.
The same applies to non-string `header` values — without `id` or a string header, construction fails.
wrong_pattern: |
// ❌ accessorFn + JSX header => no id can be derived
columnHelper.accessor((row) => row.lastName, {
header: () => <span>Last Name</span>,
cell: (info) => info.getValue(),
})
correct_pattern: |
// ✅ explicit id whenever you use accessorFn
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
header: () => <span>Last Name</span>,
cell: (info) => info.getValue(),
})
source:
packages/table-core/src/core/columns/constructColumn.ts:91-100; examples/react/basic-use-table/src/main.tsx:65-70;
docs/guide/column-defs.md:233-243
priority: CRITICAL
status: active
version_context: always present (same rule as v8)
- mistake: Defines `columns` inside the component without `useMemo`
mechanism:
'TanStack Table compares `columns` and `data` by reference. Re-creating either array on
each render triggers internal recomputation that, in React, causes an infinite loop (state change
-> render -> new `columns` -> state change). This is the #1 FAQ.'
wrong_pattern: |
function MyTable() {
// ❌ new array reference every render -> infinite render loop
const columns = [
columnHelper.accessor('firstName', { header: 'First' }),
columnHelper.accessor('lastName', { header: 'Last' }),
]
const table = useTable({ _features, _rowModels: {}, columns, data })
}
correct_pattern: |
// ✅ memoized columns or module-scoped via columnHelper.columns([...])
function MyTable() {
const columns = React.useMemo(
() =>
columnHelper.columns([
columnHelper.accessor('firstName', { header: 'First' }),
columnHelper.accessor('lastName', { header: 'Last' }),
]),
[],
)
const table = useTable({ _features, _rowModels: {}, columns, data })
}
source: docs/faq.md:5-128; examples/react/basic-subscribe/src/main.tsx:51-127
priority: CRITICAL
status: active
version_context: 'always present; #1 reason for infinite re-renders'
skills:
- setup
- state-management
- mistake: Uses array index `row.id` and updates data
mechanism:
By default, `row.id` is the row's index in `data`. When `data` is reordered, filtered,
or items are removed, row-keyed state (selection, expansion, pinning) attaches to the wrong row.
`getRowId` derives a stable id from the row's own data.
wrong_pattern: |
// ❌ no getRowId -> rowSelection survives data updates but maps to wrong rows
const table = useTable({
_features,
_rowModels: {},
columns,
data,
enableRowSelection: true,
})
correct_pattern: |
// ✅ stable id from the row's own identifier
const table = useTable({
_features,
_rowModels: {},
columns,
data,
getRowId: (row) => row.id,
enableRowSelection: true,
})
source: docs/guide/rows.md:48-59; examples/react/basic-subscribe/src/main.tsx:148; packages/table-core/src/core/rows/coreRowsFeature.utils.ts:215-228
priority: HIGH
status: active
version_context: always present
- mistake: Column visibility toggle on column groups produces incorrect state
mechanism: |
Calling `column.toggleVisibility()` on a column GROUP writes `false` to
`columnVisibility[groupId]`, but `getIsVisible()` for a group always returns
`true` regardless. Users wire up a checkbox to the group header thinking it
hides children — it doesn't.
wrong_pattern: |
// ❌ Group-level toggle silently no-ops on visibility
<Checkbox checked={column.getIsVisible()} onChange={column.toggleVisibility} />
correct_pattern: |
// ✅ Iterate leaves explicitly
function toggleGroupVisibility(group) {
const targetVisible = !group.getLeafColumns().every(c => c.getIsVisible())
group.getLeafColumns().forEach(c => c.toggleVisibility(targetVisible))
}
source:
https://github.com/TanStack/table/issues/5497 (Column visibility APIs do not work with column
groups, 5 comments)
priority: MEDIUM
status: active
version_context: v8 / v9
skills:
- column-definitions
- column-layout
- mistake: accessor with optional path strips undefined from getValue type
mechanism: |
The `DeepValue` generic in table-core walks a dotted path as `infer TBranch`/
`infer TDeepProp`, but doesn't propagate `undefined` when an intermediate key
is optional. `getValue()` returns `number` when it should return `number | undefined`,
hiding real runtime errors.
wrong_pattern: |
// ❌ amount is inferred as `number` even though salary is optional
columnHelper.accessor('user.salary.amount', {
cell: (row) => {
const amount = row.getValue() // type: number (WRONG)
return amount.toFixed(2) // crashes when salary is undefined
},
})
correct_pattern: |
// ✅ Use accessorFn for paths with optional segments — type follows expression
columnHelper.accessor((row) => row.user.salary?.amount, {
id: 'salary',
cell: (info) => {
const amount = info.getValue() // type: number | undefined
return amount?.toFixed(2) ?? '-'
},
})
source:
https://github.com/TanStack/table/issues/6238 (accessor getValue() loses undefined for key
paths with optional keys)
priority: MEDIUM
status: active
version_context: v8 + v9 type bug
- mistake: columnHelper.accessor inside columnHelper.group loses getValue type inference
mechanism: |
When an accessor is nested inside a `columnHelper.group()`, the `info.getValue()`
return type degrades to `unknown` because the group helper's overloads don't
thread the row-data generic through correctly.
wrong_pattern: |
// ❌ info.getValue() inferred as unknown
columnHelper.group({
id: 'name',
columns: [
columnHelper.accessor('firstName', {
cell: (info) => info.getValue(), // unknown
}),
],
})
correct_pattern: |
// ✅ Hoist accessor definitions out of the group
const firstNameCol = columnHelper.accessor('firstName', {
cell: (info) => info.getValue(), // string
})
columnHelper.group({ id: 'name', columns: [firstNameCol] })
source:
https://github.com/TanStack/table/issues/5860 (getValue fails to infer correct type when columnHelper.accessor
defined within columnHelper.group), https://github.com/TanStack/table/issues/5065
priority: MEDIUM
status: active
version_context: v8 / v9
- mistake: getValue cache not invalidating when accessorFn changes
mechanism: |
`getValue` caches per-column by ID. If you swap `accessorFn` in response to a
state change (e.g. "Last, First" ↔ "First Last" toggle) without changing the
column ID, cached values are returned. The new accessor never runs.
wrong_pattern: |
// ❌ Stale values shown after accessor switch
const columns = useMemo(() => [
columnHelper.accessor(
firstFormat ? (row) => row.firstName + ' ' + row.lastName : (row) => row.lastName + ', ' + row.firstName,
{ id: 'name' }
),
], [firstFormat])
correct_pattern: |
// ✅ Either change the column ID (loses sort/filter state) OR move the logic
// into cell() which is not cached:
columnHelper.accessor((row) => ({ first: row.firstName, last: row.lastName }), {
id: 'name',
cell: (info) => {
const { first, last } = info.getValue()
return firstFormat ? `${first} ${last}` : `${last}, ${first}`
},
})
source:
https://github.com/TanStack/table/issues/5363 (getValue cache not invalidating when accessorFn
is updated)
priority: MEDIUM
status: active
version_context: v8 / v9 — caching is fundamental, not a bug
skills:
- column-definitions
- column-layout
- mistake: Reimplementing what TanStack Table's built-in APIs already provide
mechanism:
TanStack Table IS a state-management coordinator with built-in APIs for nearly every state
transition (`table.setSorting`, `row.toggleSelected`, `table.nextPage`, `table.setColumnFilters`,
`column.toggleVisibility`, …). Agents often write their own setState logic, click handlers, or sort/filter
loops rather than using the built-ins, producing more code that is also less correct (skips internal
invariants, breaks reset APIs).
wrong_pattern: |-
// ❌ Reimplements sorting state manually instead of using the API
const [sorting, setSorting] = useState([])
const sortedData = useMemo(() => [...data].sort((a,b) => /* …custom… */), [data, sorting])
// then uses sortedData directly, bypassing the table
correct_pattern: |-
// ✅ Use the built-in APIs — table handles state, reset, multi-sort, etc.
const table = useTable({
_features: tableFeatures({ rowSortingFeature }),
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
})
// then: table.setSorting(...), column.toggleSorting(), header.getToggleSortingHandler()
priority: CRITICAL
version_context:
'Always present. The maintainer flags this as the #1 tell that "an AI wrote this."
See `setSorting`/`setColumnFilters`/`toggleSelected`/`nextPage`/etc.'
source: maintainer interview (Phase 4, 2026-05-17)
status: active
maintainer_notes:
- accessorKey + deep keys may be simplified in v10 (accessorFn + id is the maintainer's preferred long-term
shape). For v9, accessorKey remains fully supported and the more concise option for simple keys.
- name: State Management
slug: state-management
domain: core-foundations
description:
Coordinate table state slices (sorting, pagination, filters, selection, etc.) across `initialState`,
`state`+`on*Change`, and external `atoms`, with awareness of v9 atom precedence.
type: core
packages:
- '@tanstack/table-core'
covers:
- baseAtoms
- atoms
- store
- state
- initialState
- on[State]Change
- setOptions
- reset
- resetSorting
- resetPagination
- resetColumnFilters
- resetGlobalFilter
- resetRowSelection
- manualFiltering
- manualSorting
- manualPagination
- manualExpanding
- manualGrouping
- autoResetPageIndex
- autoResetAll
- TableState
- SortingState
- PaginationState
- RowSelectionState
- ColumnFiltersState
- GroupingState
- storeReactivityBindings
- createAtom
- useCreateAtom
- useSelector
- Subscribe
- table.Subscribe
- useTable selector (second argument)
tasks:
- Decide which state ownership pattern fits — internal (default) vs `state`+`on*Change` (v8 controlled)
vs external `atoms` (v9 preferred) vs `initialState` (starting value only)
- Promote a slice to external state when the app needs to share it with a server query, persistence,
or another component
- Toggle a feature to a "manual" server-side mode (`manualSorting`, `manualFiltering`, `manualPagination`)
— sort/filter/paginate happen server-side and the table just renders the page
- Reset state with feature reset APIs (`resetSorting()`, `resetPagination(true)`) and understand why
`table.reset()` is unsafe when slices are externally owned
failure_modes:
- mistake: Passes both `state.pagination` and `atoms.pagination`
mechanism:
When both are supplied for the same slice, the external atom wins silently. `state.pagination`
is then dead config — UI values written to React state never reach the table, leading to "I called
setPagination but nothing updated" bugs.
wrong_pattern: |
// ❌ both ownership paths for the same slice
const paginationAtom = useCreateAtom<PaginationState>({ pageIndex: 0, pageSize: 10 })
const [pagination, setPagination] = React.useState(...)
const table = useTable({
_features, _rowModels: {...}, columns, data,
state: { pagination }, // ignored
onPaginationChange: setPagination,
atoms: { pagination: paginationAtom }, // wins
})
correct_pattern: |
// ✅ pick one ownership path per slice — here, external atoms
const paginationAtom = useCreateAtom<PaginationState>({ pageIndex: 0, pageSize: 10 })
const table = useTable({
_features, _rowModels: {...}, columns, data,
atoms: { pagination: paginationAtom },
// no state.pagination, no onPaginationChange needed
})
source:
docs/framework/react/guide/table-state.md:315-316; docs/framework/react/guide/migrating.md:465-481;
packages/table-core/src/core/table/constructTable.ts:93-103 (atom precedence)
priority: CRITICAL
status: active
version_context: v9 alpha; new ownership-conflict surface introduced by the `atoms` option
- mistake: Uses external `state` without the matching `on*Change` callback
mechanism:
External `state.sorting` syncs into the table's base atom, but without `onSortingChange`
the table has no way to update React state — every sort toggle appears to do nothing. v9 silently
keeps reading from `state` so the UI looks "stuck".
wrong_pattern: |
// ❌ state without callback — sort toggles never reach setSorting
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useTable({
_features, _rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
state: { sorting }, // no onSortingChange
})
correct_pattern: |
// ✅ state + on*Change must be paired
const [sorting, setSorting] = React.useState<SortingState>([])
const table = useTable({
_features, _rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
state: { sorting },
onSortingChange: setSorting,
})
source: docs/framework/react/guide/table-state.md:415-435; examples/react/basic-external-state/src/main.tsx:64-94
priority: CRITICAL
status: active
version_context: always present (same v8 rule)
- mistake: Uses `initialState` to control or update state
mechanism:
'`initialState` is only read once at construction time to build base atoms; mutating it
later does NOT update table state. Developers expect a React-state-like contract and watch state
freeze in place.'
wrong_pattern: |
// ❌ updates to initialState are ignored after first render
function MyTable({ defaultSort }: { defaultSort: SortingState }) {
const table = useTable({
_features, _rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
initialState: { sorting: defaultSort }, // ❌ later changes to defaultSort never sync
})
}
correct_pattern: |
// ✅ control with state + on*Change (or an external atom)
function MyTable({ defaultSort }: { defaultSort: SortingState }) {
const [sorting, setSorting] = React.useState(defaultSort)
const table = useTable({
_features, _rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
state: { sorting },
onSortingChange: setSorting,
})
}
source: docs/framework/vanilla/guide/table-state.md:142-175; docs/framework/react/guide/table-state.md:285-315
priority: HIGH
status: active
version_context: always present (same v8 rule)
- mistake: Writes to `table.baseAtoms.x.set(...)` while `atoms.x` owns the slice
mechanism:
When an external atom is supplied for a slice, `table.atoms.x` derives from it, not from
`baseAtoms.x`. Writes to the base atom silently no-op for reads; the UI shows the external atom's
value, the base atom drifts, and reset behavior gets weird.
wrong_pattern: |
// ❌ direct baseAtoms write while external atom owns the slice
const paginationAtom = useCreateAtom<PaginationState>({ pageIndex: 0, pageSize: 10 })
const table = useTable({ _features, _rowModels: {...}, columns, data, atoms: { pagination: paginationAtom } })
// later, somewhere
table.baseAtoms.pagination.set((old) => ({ ...old, pageIndex: 0 }))
// ❌ baseAtom updated, but table.atoms.pagination still reads from paginationAtom
correct_pattern: |
// ✅ write to the external atom (or use the feature's setter API)
paginationAtom.set((old) => ({ ...old, pageIndex: 0 }))
// or
table.setPageIndex(0)
source:
docs/framework/vanilla/guide/table-state.md:138-141; docs/framework/react/guide/table-state.md:280-283;
packages/table-core/src/core/table/constructTable.ts:93-103
priority: HIGH
status: active
version_context: v9 alpha; new pitfall introduced by atom architecture
- mistake: Forgets `manualSorting`/`manualFiltering`/`manualPagination` when serving server-side data
mechanism:
'Without the `manual*` flag, the table re-applies its client-side row models on top of
already-sorted/filtered/paginated server data — rows get re-sorted, re-filtered, or sliced into
another page. Symptoms: wrong rows shown, broken page math, blank pages.'
wrong_pattern: |
// ❌ data is already paginated server-side, but table still slices it
const dataQuery = useQuery({ queryKey: ['data', pagination], queryFn: fetchPage })
const table = useTable({
_features, _rowModels: { paginatedRowModel: createPaginatedRowModel() },
columns,
data: dataQuery.data?.rows ?? [],
rowCount: dataQuery.data?.rowCount,
atoms: { pagination: paginationAtom },
// ❌ missing manualPagination: true
})
correct_pattern: |
// ✅ tell the table the server handles pagination
const table = useTable({
_features, _rowModels: {}, // can drop paginatedRowModel if fully server-side
columns,
data: dataQuery.data?.rows ?? [],
rowCount: dataQuery.data?.rowCount,
atoms: { pagination: paginationAtom },
manualPagination: true,
})
source:
docs/framework/vanilla/guide/table-state.md:197-228; docs/framework/react/guide/table-state.md:336-378;
packages/table-core/src/features/row-pagination/rowPaginationFeature.types.ts:20-23
priority: CRITICAL
status: active
version_context: always present (same v8 rule)
- mistake: Uses `table.reset()` to clear externally owned state
mechanism:
'`table.reset()` only resets the internal `baseAtoms` to `initialState`; slices owned by
external atoms or external `state` are untouched. Developers expect "reset everything" and end up
with half-reset state (visible state from external atom, hidden state in baseAtom drifts).'
wrong_pattern: |
// ❌ external atom keeps its current value; only baseAtoms reset
const sortingAtom = useCreateAtom<SortingState>([])
const table = useTable({
_features, _rowModels: {...}, columns, data,
atoms: { sorting: sortingAtom },
})
// ...
table.reset() // sortingAtom is NOT cleared
correct_pattern: |
// ✅ feature-specific reset writes through the slice's updater (atom-aware)
table.resetSorting() // works whether sorting is internal or external
// or, if you specifically want to clear the external atom:
sortingAtom.set([])
source:
docs/framework/vanilla/guide/table-state.md:177-188; docs/framework/react/guide/table-state.md:317-328;
packages/table-core/src/core/table/coreTablesFeature.utils.ts:42-65
priority: HIGH
status: active
version_context: v9 alpha; the atom split makes `reset()` less safe than v8
- mistake: Reimplementing what TanStack Table's built-in APIs already provide
mechanism:
TanStack Table IS a state-management coordinator with built-in APIs for nearly every state
transition (`table.setSorting`, `row.toggleSelected`, `table.nextPage`, `table.setColumnFilters`,
`column.toggleVisibility`, …). Agents often write their own setState logic, click handlers, or sort/filter
loops rather than using the built-ins, producing more code that is also less correct (skips internal
invariants, breaks reset APIs).
wrong_pattern: |-
// ❌ Reimplements sorting state manually instead of using the API
const [sorting, setSorting] = useState([])
const sortedData = useMemo(() => [...data].sort((a,b) => /* …custom… */), [data, sorting])
// then uses sortedData directly, bypassing the table
correct_pattern: |-
// ✅ Use the built-in APIs — table handles state, reset, multi-sort, etc.
const table = useTable({
_features: tableFeatures({ rowSortingFeature }),
_rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
columns, data,
})
// then: table.setSorting(...), column.toggleSorting(), header.getToggleSortingHandler()
priority: CRITICAL
version_context:
'Always present. The maintainer flags this as the #1 tell that "an AI wrote this."
See `setSorting`/`setColumnFilters`/`toggleSelected`/`nextPage`/etc.'
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- mistake: API or state slice "missing" because the feature was not registered in `_features`
mechanism:
In v9, `_features` is a tree-shakeable registry. If a feature is not in `_features`, TypeScript
hides its APIs and the runtime atom is not created. Agents who copy a snippet for `table.setColumnFilters(...)`
without registering `columnFilteringFeature` see a TS error or `table.atoms.columnFilters` is undefined
— and may incorrectly conclude the feature is broken or removed in v9.
wrong_pattern: |-
// ❌ rowSortingFeature missing — table.setSorting / state.sorting unavailable
const _features = tableFeatures({}) // empty
const table = useTable({ _features, _rowModels: {}, columns, data })
table.setSorting([{ id: 'age', desc: true }]) // ❌ does not exist on this table type
correct_pattern: |-
// ✅ Register every feature you intend to use; pair with its row model when applicable
const _features = tableFeatures({ rowSortingFeature, rowPaginationFeature })
const table = useTable({
_features,
_rowModels: {
sortedRowModel: createSortedRowModel(sortFns),
paginatedRowModel: createPaginatedRowModel(),
},
columns, data,
})
priority: CRITICAL
version_context:
v9-specific. This is the first version of TanStack Table where features must be declared
for TypeScript and runtime to expose them. Expect heavy confusion from devs and agents trained on
v8.
source: maintainer interview (Phase 4, 2026-05-17)
status: active
- name: Customizing Feature Behavior
slug: customizing-feature-behavior
domain: core-foundations
description:
Override per-column `sortFn`, `filterFn`, `aggregationFn`, and table-level `globalFilterFn`
— and chain filter→sort metadata via `addMeta`.
type: core
packages:
- '@tanstack/table-core'
covers:
- filterFn
- sortFn
- aggregationFn
- globalFilterFn
- FilterFn
- SortFn
- AggregationFn
- addMeta
- autoRemove
- resolveFilterValue
- columnFiltersMeta
- filterFns
- sortFns
- aggregationFns
- FilterFnOption
- SortFnOption
- AggregationFnOption
- BuiltInFilterFn
- BuiltInSortFn
- BuiltInAggregationFn
- invertSorting
- sortUndefined
- sortDescFirst
tasks:
- Author a custom `filterFn` (e.g. fuzzy filter) and reference it by name from a column's `filterFn`
option after registering it via `createFilteredRowModel({ ...filterFns, custom })`
- 'Use the `addMeta` argument inside a `filterFn` to stash ranking info on the row, then read it from
a custom `sortFn` via `row.columnFiltersMeta[columnId]` for filter→sort handoff (canonical: match-sorter-utils
fuzzy)'
- Pick a built-in `sortFn` by string name (`alphanumeric`, `text`, `datetime`, `basic`) for a column,
then layer `invertSorting`/`sortDescFirst`/`sortUndefined` for direction control
- Register a custom `aggregationFn` for a grouped column (e.g. weighted average) by passing it through