fix(table-core): wire expansion auto-reset into the core row model and guard first runs - #6499
Conversation
…d guard first runs Auto-resets are `onAfterUpdate` hooks on the row model stage memos. Two related defects made them fire in the wrong places. Expansion auto-reset was only wired from the grouped row model, so `autoResetExpanded` silently did nothing for tables that use expansion without grouping (#5801). It is now wired from `createCoreRowModel` alongside the existing pageIndex, sorting, and cell-selection resets, so a data reference change resets expansion regardless of which features are installed. `table_autoResetExpanded` gained the same feature guard the sorting and cell-selection resets already use, since the core row model runs on tables without the expanding feature. Auto-resets also fired on the very first computation of every stage (#5968). `memo` seeds its dependency list to `[]`, so the initial run always compares as changed, and merely reading a row model on mount scheduled resets. For uncontrolled tables this was self-cancelling (the reset targets `initialState`), which is why it went unnoticed, but it wiped a seeded `initialState.pagination.pageIndex` and pushed unsolicited `onExpandedChange` / `onPaginationChange` calls at controlled consumers on mount. v7 and v8 suppressed the first run with a `registered` flag; the v9 rewrite dropped it. A new `skipFirstRun` util restores that suppression. It is applied in the row model factories, which already run once per table, so each table gets its own flag. The grouped row model tracked previous inputs already and only needed its first-run condition inverted. The worker bridge reports every stage as changed in its first response, so it carries an equivalent `hasAppliedResults` flag. Landing order matters: wiring expansion into the core row model without the first-run guard would have extended the mount-time wipe from grouped tables to every table using expansion, so both halves ship together. - fix: wire `table_autoResetExpanded` into `createCoreRowModel` (#5801) - fix: skip auto-resets on the first computation of each stage (#5968) - add `skipFirstRun` util; apply to core, filtered, and sorted row models - invert the grouped row model's first-run condition - guard `table_autoResetExpanded` when the expanding feature is absent - skip auto-resets on the worker bridge's first applied result - tests: expansion reset without the grouping feature; first-run guard suite covering seeded initial state and controlled-state consumers Examples: migrate the basic examples to `createColumnHelper` across all frameworks, document more table options inline as commented-out defaults, and replace the `filterFns.between` workaround in the React expanding example with the `filterFn_between` individual export that already existed. Closes #5801 Closes #5968 Supersedes #6443 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
View your CI Pipeline Execution ↗ for commit 1189c33
☁️ Nx Cloud last updated this comment at |
📝 WalkthroughWalkthroughThe change updates table-core auto-reset behavior, prevents first-result worker resets, adds Angular expanding-example filters, and refreshes table configuration examples across supported frameworks. ChangesTable auto-reset behavior
Angular expanding filters
Framework examples
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/angular/row-pinning/src/app/app.ts`:
- Around line 76-79: Align the row-pinning example’s initial state with its
controlled state: update the row-pinning source used by this.rowPinning() to
initialize with the documented row IDs, or remove the controlled rowPinning
state so initialState.rowPinning takes effect. Preserve the intended
first-render pinning behavior and avoid leaving conflicting controlled and
initial row-pinning configurations.
In `@examples/ember/row-pinning/app/templates/application.gts`:
- Around line 117-120: Update the initial row-pinning example in the application
template so it is executable: either remove the controlled empty rowPinning
state and enable initialState.rowPinning, or initialize the controlled state
with the row IDs generated by getRowId. Ensure the pinned IDs match the actual
getRowId output rather than hard-coded '0' and '1'.
In `@examples/octane/row-pinning/src/main.tsrx`:
- Around line 164-165: Align the commented initialState.rowPinning example with
the controlled state by initializing controlled state.rowPinning with top ID "0"
and bottom ID "1", so uncommenting the option preserves the demonstrated pinned
rows on first render.
In `@examples/react/row-pinning/src/main.tsx`:
- Around line 163-166: Update the initialState configuration in
examples/react/row-pinning/src/main.tsx lines 163-166,
examples/preact/row-pinning/src/main.tsx lines 162-165, and
examples/solid/row-pinning/src/App.tsx lines 141-144 to seed rowPinning with top
row "0" and bottom row "1", while preserving the existing pagination settings
and documentation.
In `@examples/svelte/row-pinning/src/App.svelte`:
- Around line 92-95: Update the controlled row-pinning state in
createTableState<RowPinningState> to initialize top with ['0'] and bottom with
['1']; do not rely on initialState.rowPinning, since
table.options.state.rowPinning controls the first render.
In `@packages/table-core/src/core/row-models/createCoreRowModel.ts`:
- Around line 31-36: Update the onAfterUpdate callback in createCoreRowModel so
data updates no longer request table_autoResetExpanded when the
grouped-row-model path will also reset it. Preserve table_autoResetExpanded in
createGroupedRowModel for grouping or filtering changes that are not already
reset by the core stage, while keeping the other core reset calls unchanged.
In `@packages/table-core/src/worker/createTableWorker.ts`:
- Around line 75-76: Update the worker termination loop in terminate() to reset
hasAppliedResults to false for each retained bridge before or alongside
terminating it, ensuring the recreated worker treats its first result as the
initial result.
- Around line 256-260: Update the change tracking in the worker result
application flow around isFirstAppliedResult so core-stage changes are tracked
separately from grouped-stage changes. Ensure expanded state is reset when
either the core stage or grouped stage changes, while preserving the
first-applied-result guard and existing auto-reset behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ad769a99-aa14-4d3c-84a0-6ac29e01464b
📒 Files selected for processing (74)
examples/alpine/aggregation/src/main.tsexamples/alpine/basic-create-table/index.htmlexamples/alpine/basic-create-table/src/main.tsexamples/alpine/column-sizing/src/main.tsexamples/alpine/expanding/src/main.tsexamples/alpine/row-pinning/src/main.tsexamples/alpine/row-selection/src/main.tsexamples/angular/aggregation/src/app/app.tsexamples/angular/basic-inject-table/src/app/app.tsexamples/angular/column-sizing/src/app/app.tsexamples/angular/expanding/src/app/app.htmlexamples/angular/expanding/src/app/app.tsexamples/angular/row-pinning/src/app/app.tsexamples/angular/row-selection/src/app/app.tsexamples/ember/aggregation/app/templates/application.gtsexamples/ember/column-sizing/app/templates/application.gtsexamples/ember/expanding/app/templates/application.gtsexamples/ember/row-pinning/app/templates/application.gtsexamples/ember/row-selection/app/templates/application.gtsexamples/lit/aggregation/src/main.tsexamples/lit/column-sizing/src/main.tsexamples/lit/expanding/src/main.tsexamples/lit/row-pinning/src/main.tsexamples/lit/row-selection/src/main.tsexamples/octane/aggregation/src/main.tsrxexamples/octane/basic-use-table/src/main.tsrxexamples/octane/column-sizing/src/main.tsrxexamples/octane/expanding/src/main.tsrxexamples/octane/row-pinning/src/main.tsrxexamples/octane/row-selection/src/main.tsrxexamples/preact/aggregation/src/main.tsxexamples/preact/basic-use-table/src/main.tsxexamples/preact/column-sizing/src/main.tsxexamples/preact/expanding/src/main.tsxexamples/preact/row-pinning/src/main.tsxexamples/preact/row-selection/src/main.tsxexamples/react/aggregation/src/main.tsxexamples/react/basic-use-table/src/main.tsxexamples/react/column-pinning/src/main.tsxexamples/react/column-resizing/src/main.tsxexamples/react/column-sizing/src/main.tsxexamples/react/column-visibility/src/main.tsxexamples/react/expanding/src/main.tsxexamples/react/filters/src/main.tsxexamples/react/grouping/src/main.tsxexamples/react/row-pinning/src/main.tsxexamples/react/row-selection/src/main.tsxexamples/solid/aggregation/src/App.tsxexamples/solid/basic-use-table/src/App.tsxexamples/solid/column-sizing/src/App.tsxexamples/solid/expanding/src/App.tsxexamples/solid/row-pinning/src/App.tsxexamples/solid/row-selection/src/App.tsxexamples/svelte/aggregation/src/App.svelteexamples/svelte/basic-create-table/src/App.svelteexamples/svelte/column-sizing/src/App.svelteexamples/svelte/expanding/src/App.svelteexamples/svelte/row-pinning/src/App.svelteexamples/svelte/row-selection/src/App.svelteexamples/vanilla/aggregation/src/main.tsexamples/vue/aggregation/src/App.vueexamples/vue/basic-use-table/src/App.tsxexamples/vue/column-sizing/src/App.vueexamples/vue/expanding/src/App.tsxexamples/vue/row-pinning/src/App.tsxexamples/vue/row-selection/src/App.vuepackages/table-core/src/core/row-models/createCoreRowModel.tspackages/table-core/src/features/column-filtering/createFilteredRowModel.tspackages/table-core/src/features/column-grouping/createGroupedRowModel.tspackages/table-core/src/features/row-expanding/rowExpandingFeature.utils.tspackages/table-core/src/features/row-sorting/createSortedRowModel.tspackages/table-core/src/utils.tspackages/table-core/src/worker/createTableWorker.tspackages/table-core/tests/implementation/core/autoReset.test.ts
| initialState: { | ||
| pagination: { pageSize: 20, pageIndex: 0 }, | ||
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file info =="
fd -a 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null || true
echo "== git status/stat =="
git diff --stat HEAD~1..HEAD 2>/dev/null || git diff --stat || true
echo "== relevant lines =="
for f in $(fd 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null); do
echo "--- $f"
wc -l "$f"
sed -n '1,140p' "$f" | nl -ba | sed -n '50,110p'
done
echo "== rowPinning occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" examples/angular/row-pinning/src/app examples/angular/row-pinning 2>/dev/null || trueRepository: TanStack/table
Length of output: 5005
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== file info =="
fd -a 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null || true
echo "== git status/stat =="
git diff --stat HEAD~1.. is-available 2>/dev/null || git diff --stat || true
echo "== relevant lines =="
for f in $(fd 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null); do
echo "--- $f"
wc -l "$f"
sed -n '1,140p' "$f" | nl -ba | sed -n '50,110p'
done
echo "== rowPinning occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" examples/angular/row-pinning/src/app examples/angular/row-pinning 2>/dev/null || trueRepository: TanStack/table
Length of output: 411
🏁 Script executed:
#!/bin/bash
set -euo pipefail
f="examples/angular/row-pinning/src/app/app.ts"
echo "== app.ts lines 50-110 =="
awk 'NR>=50 && NR<=110 { printf "%4d %s\n", NR, $0 }' "$f"
echo "== all rowPinning/initialState state occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" "$f" . 2>/dev/null || true
echo "== package/source references =="
rg -n "rowPinning|initialState|rowModels|pin" examples/angular packages angular 2>/dev/null | head -200 || trueRepository: TanStack/table
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
f="examples/angular/row-pinning/src/app/app.ts"
echo "== app.ts relevant sections =="
awk 'NR>=1 && NR<=125 { printf "%4d %s\n", NR, $0 }' "$f"
echo "== focused rowPinning occurrences only in target example =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" "$f" || true
echo "== source control precedence definitions =="
rg -n "initialState|external atom|state|atoms|rowPinningFeature|rowPinning:" packages/framework docs/framework/angular/guide/table-state.md docs/framework/angular/quick-start.md | head -120 || trueRepository: TanStack/table
Length of output: 17447
Make the row-pinning initial state match the controlled row-pinning source.
state.rowPinning controls row pinning from this.rowPinning(), which starts as { top: [], bottom: [] }. Uncommenting initialState.rowPinning will not pin rows because controlled state takes precedence. Remove controlled rowPinning for this example, or initialize this.rowPinning with the documented row IDs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/angular/row-pinning/src/app/app.ts` around lines 76 - 79, Align the
row-pinning example’s initial state with its controlled state: update the
row-pinning source used by this.rowPinning() to initialize with the documented
row IDs, or remove the controlled rowPinning state so initialState.rowPinning
takes effect. Preserve the intended first-render pinning behavior and avoid
leaving conflicting controlled and initial row-pinning configurations.
| initialState: { | ||
| pagination: { pageSize: 20, pageIndex: 0 }, | ||
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the initial row-pinning example executable.
If Line 119 is uncommented, state.rowPinning still supplies the empty this.rowPinning value. The initialState.rowPinning value is therefore not applied. The IDs '0' and '1' also do not match the IDs returned by getRowId at Lines 114-115.
Show this as an uncontrolled example, or initialize the controlled state with IDs produced by getRowId.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/ember/row-pinning/app/templates/application.gts` around lines 117 -
120, Update the initial row-pinning example in the application template so it is
executable: either remove the controlled empty rowPinning state and enable
initialState.rowPinning, or initialize the controlled state with the row IDs
generated by getRowId. Ensure the pinned IDs match the actual getRowId output
rather than hard-coded '0' and '1'.
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 3 'initialState|rowPinning|onRowPinningChange' examples/octane/row-pinning/src/main.tsrxRepository: TanStack/table
Length of output: 1476
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n 'function useTable|const useTable|rowPinningFeature|state: \\{|initialState' packages -g '*.ts' -g '*.tsx' | head -200Repository: TanStack/table
Length of output: 381
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n 'function useTable|const useTable|rowPinningFeature|initialState|state.pinning|state.rowPinning' packages -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' | head -300Repository: TanStack/table
Length of output: 37838
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -a 'createTableHook.tsx|useTable.ts|rowPinningFeature.utils.ts|rowPinningFeature.ts' packages/octane-table packages/table-core src packages 2>/dev/null | sed 's#^\./##' | head -50Repository: TanStack/table
Length of output: 1110
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '220,340p' packages/octane-table/src/createTableHook.tsx
sed -n '1,130p' packages/octane-table/src/useTable.tsrx
sed -n '1,80p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts
sed -n '1,70p' packages/table-core/src/features/row-pinning/rowPinningFeature.tsRepository: TanStack/table
Length of output: 241
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git ls-files packages/octane-table packages/table-core | rg 'createTableHook|useTable|rowPinningFeature'Repository: TanStack/table
Length of output: 771
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '220,345p' packages/octane-table/src/createTableHook.tsrx
printf '\n--- oct useTable ---\n'
sed -n '1,140p' packages/octane-table/src/useTable.tsrx
printf '\n--- row pinning utils ---\n'
sed -n '1,90p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts
printf '\n--- row pinning feature ---\n'
sed -n '1,75p' packages/table-core/src/features/row-pinning/rowPinningFeature.ts
printf '\n--- tests ---\n'
sed -n '200,280p' packages/table-core/tests/unit/features/row-pinning/rowPinningFeature.utils.test.tsRepository: TanStack/table
Length of output: 14524
Align the commented pinning example with the controlled state.
Controlled state.rowPinning is initialized with top: [] and bottom: []. If users uncomment the initialState.rowPinning option, those rows are still unpinned on first render because the controlled value wins.
Update the example to initialize the controlled state with these IDs, or make the comment say the option should replace the controlled rowPinning state.
Suggested documentation fix
- // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
+ // rowPinning: { top: ['0'], bottom: ['1'] }, // use instead of controlled state.rowPinning above📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | |
| }, | |
| // rowPinning: { top: ['0'], bottom: ['1'] }, // use instead of controlled state.rowPinning above | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/octane/row-pinning/src/main.tsrx` around lines 164 - 165, Align the
commented initialState.rowPinning example with the controlled state by
initializing controlled state.rowPinning with top ID "0" and bottom ID "1", so
uncommenting the option preserves the demonstrated pinned rows on first render.
| initialState: { | ||
| pagination: { pageSize: 20, pageIndex: 0 }, | ||
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C 5 'initialState|options\.state|state\(' packages/table-core/src
for file in \
examples/react/row-pinning/src/main.tsx \
examples/preact/row-pinning/src/main.tsx \
examples/solid/row-pinning/src/App.tsx
do
rg -n -C 5 'initialState|rowPinning|onRowPinningChange|state' "$file"
doneRepository: TanStack/table
Length of output: 50373
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the table implementation files and inspect table creation/state merge paths.
fd -a 'createTable.* | Table.* | Table.*\.ts$' packages/table-core/src | sed 's#^\./##' | head -80
printf '\n--- candidate createTable/getInitialState references ---\n'
rg -n -C 4 'getInitialState|initialState|createTable|initial.*state|state.*initial' packages/table-core/src/core packages/table-core/src/types packages/table-core/src/queries packages/table-core/src/features/row-pinning packages/table-core/src/features/column-ordering packages/table-core/src/features/column-resizing packages/table-core/src/features/row-selection packages/table-core/src/features/global-filtering packages/table-core/src/table | head -240
printf '\n--- rowPinning example relevant snippets ---\n'
for file in \
examples/react/row-pinning/src/main.tsx \
examples/preact/row-pinning/src/main.tsx \
examples/solid/row-pinning/src/App.tsx
do
echo "### $file"
sed -n '130,200p' "$file"
doneRepository: TanStack/table
Length of output: 24075
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- table-core files ---'
git ls-files packages/table-core/src | rg 'createTable|Table\.ts|Atoms|Store|Atoms|utils|create.*Table|table.*' | head -120
printf '%s\n' '--- focused getInitialState state merging ---'
rg -n -C 5 'getInitialState|initialState|state\?' packages/table-core/src --glob '*.ts' | head -260
printf '%s\n' '--- rowPinningFeature details ---'
sed -n '1,90p' packages/table-core/src/features/row-pinning/rowPinningFeature.ts
sed -n '1,110p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts
printf '%s\n' '--- row-pinning examples ---'
for file in \
examples/react/row-pinning/src/main.tsx \
examples/preact/row-pinning/src/main.tsx \
examples/solid/row-pinning/src/App.tsx
do
echo "### $file"
rg -n -C 6 'initialState|rowPinning|onRowPinningChange|rowPinning' "$file"
doneRepository: TanStack/table
Length of output: 43828
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- core table construction ---'
sed -n '1,260p' packages/table-core/src/core/table/constructTable.ts
printf '%s\n' '--- table options normalization ---'
fd -a '.*' packages/table-core/src | rg 'TableOptions|options' | sed 's#^\./##'
sed -n '1,220p' packages/table-core/src/types/TableOptions.ts
sed -n '1,220p' packages/table-core/src/types/TableState.ts
printf '%s\n' '--- table option merge searches ---'
rg -n -C 5 'merge|tableOptions|getDeep`|createTable|initialState|state.*' packages/table-core/src/core packages/table-core/src/flex-render.ts packages/table-core/src/static-functions.ts packages/table-core/src/helpers/tableOptions.ts --glob '*.ts' | head -260Repository: TanStack/table
Length of output: 42345
Seed controlled row-pinning state for initial pins.
state.rowPinning overrides initialState.rowPinning on the first render, so these examples seed empty pinning state while documenting initial pinned rows. Seed rowPinning with { top: ['0'], bottom: ['1'] } in all three examples if initial pins should be shown.
examples/react/row-pinning/src/main.tsxexamples/preact/row-pinning/src/main.tsxexamples/solid/row-pinning/src/App.tsx
📍 Affects 3 files
examples/react/row-pinning/src/main.tsx#L163-L166(this comment)examples/preact/row-pinning/src/main.tsx#L162-L165examples/solid/row-pinning/src/App.tsx#L141-L144
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/react/row-pinning/src/main.tsx` around lines 163 - 166, Update the
initialState configuration in examples/react/row-pinning/src/main.tsx lines
163-166, examples/preact/row-pinning/src/main.tsx lines 162-165, and
examples/solid/row-pinning/src/App.tsx lines 141-144 to seed rowPinning with top
row "0" and bottom row "1", while preserving the existing pagination settings
and documentation.
| initialState: { | ||
| pagination: { pageSize: 20, pageIndex: 0 }, | ||
| // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files matching App.svelte:"
git ls-files | grep 'examples/svelte/row-pinning/src/App.svelte' || true
echo
echo "Relevant sections:"
if [ -f examples/svelte/row-pinning/src/App.svelte ]; then
nl -ba examples/svelte/row-pinning/src/App.svelte | sed -n '1,140p'
fi
echo
echo "Search for createTableState / rowPinning usage:"
rg -n "createTableState|rowPinning|initialState" examples/svelte/row-pinning/src/App.svelte examples . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: TanStack/table
Length of output: 283
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Top 140 lines of examples/svelte/row-pinning/src/App.svelte:"
awk '{printf "%6d\t%s\n", NR, $0}' examples/svelte/row-pinning/src/App.svelte | sed -n '1,140p'
echo
echo "Search for createTableState / rowPinning / initialState:"
rg -n "createTableState|rowPinning|initialState" examples/svelte/row-pinning/src/App.svelte examples . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: TanStack/table
Length of output: 32863
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package files and references:"
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' | head -50
rg -n "packages/table-core/src/createTable.ts|createTable\\(|initialState|atoms\\.rowPinning|getDefaultRowPinningState|rowPinning\\.initialState" packages tables svelte-table --glob '!node_modules' --glob '!dist' --glob '!build' | head -250
echo
echo "Candidate createTable source snippets:"
if [ -f packages/table-core/src/createTable.ts ]; then
wc -l packages/table-core/src/createTable.ts
rg -n -C 6 "initialState|setDefault|rowPinning|createTableState|onRowPinningChange|setRowPinning" packages/table-core/src/createTable.ts packages/table-core/src --glob '!node_modules' --glob '!dist' --glob '!build' | head -300
fi
echo
echo "Relevant Svelte store/state creation:"
if [ -f packages/svelte-table/src/createTableState.svelte.ts ]; then
wc -l packages/svelte-table/src/createTableState.svelte.ts
sed -n '1,240p' packages/svelte-table/src/createTableState.svelte.ts
fiRepository: TanStack/table
Length of output: 2478
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repo root contents:"
git ls-files | sed 's#/.*##' | sort | uniq | head -100
echo
echo "Locate createTable.ts:"
git ls-files | rg 'createTable\.ts$|createTableState\.svelte\.ts$|rowPinningFeature\.utils\.ts$' | sed -n '1,120p'
echo
echo "Search createTable/rowPinning in tracked files:"
rg -n "function createTable|const createTable|initialState:|rowPinning:|atoms\\.rowPinning|getDefaultRowPinningState|onRowPinningChange|setRowPinning" packages examples packages --glob '!node_modules' --glob '!dist' --glob '!build' | head -300Repository: TanStack/table
Length of output: 40071
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "SvelteTable createTable.ts:"
git ls-files | rg 'packages/[^/]*/src/createTable\.ts$' | rg -i 'svelte'
for f in $(git ls-files | rg 'packages/[^/]*/src/createTable\.ts$' | rg -i 'svelte'); do
echo "--- $f"
wc -l "$f"
sed -n '1,180p' "$f"
done
echo
echo "Focus row pinning tests for controlled initialState precedence:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts | sed -n '1,200p'
echo
echo "Inspect default row pinning atom source:"
rg -n -C 8 "DEFAULT|EMPTY_PINNING_STATE|rowPinning:|getDefaultRowPinningState|atoms\\.rowPinning|initialState\\[.*rowPinning|options\\.initialState|onRowPinningChange" packages/table-core/src packages --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,260p'Repository: TanStack/table
Length of output: 180
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "List tracked createTable.ts files:"
git ls-files 'packages/*/src/createTable.ts' | sed -n '1,80p'
echo
echo "Svelte package src files:"
git ls-files packages/svelte-table/src | sed -n '1,120p'
echo
echo "Search export/createTable/createTableState:"
rg -n -C 5 "export .*createTable|function createTable|setRowPinning|onRowPinningChange|rowPinning|initialState|atoms" packages/table-core/src packages/svelte-table/src packages/preact-table/src packages/react-table/src packages/solid-table/src --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'Repository: TanStack/table
Length of output: 29098
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Svelte createTable state synchronization:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/svelte-table/src/createTable.svelte.ts | sed -n '1,160p'
echo
echo "Svelte createTableState:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/svelte-table/src/createTableState.svelte.ts | sed -n '1,220p'
echo
echo "Core options store atom initialization and apply options:"
rg -n -C 8 "optionsStore|initialState|apply|state\\(|setOptions|rowPinning|rowPinningFeature|getDefaultRowPinningState|EMPTY_PINNING_STATE" packages/table-core/src packages/table-core/src -g '!node_modules' -g '!dist' -g '!build' | sed -n '1,260p'
echo
echo "Core table store/source atom for state:"
rg -n -C 6 "sourceAtom|source:|atoms\\.state|table\\.state|setOptions|effect.*state|state.\\(" packages/table-core/src --global '!node_modules' '!dist' '!build' | sed -n '1,260p'Repository: TanStack/table
Length of output: 29989
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Core state atom/sourceAtom definitions:"
rg -n -C 8 "sourceAtom|source:|sourceAtom|atoms\\.state|setOptions|initialState\\[" packages/table-core/src --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'
echo
echo "Row pinning source definitions:"
rg -n -C 8 "EMPTY_PINNING_STATE|getDefaultRowPinningState|rowPinning\\)|resetRowPinning|setRowPinning|getInitialState: \\(initialState\\).* initialState|onRowPinningChange" packages/table-core/src/features/row-pinning --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'
echo
echo "Focused row pinning tests:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts | sed -n '90,200p'Repository: TanStack/table
Length of output: 40796
Use the controlled pinning state as the initial value here.
This example keeps rowPinning in createTableState, so uncommenting initialState.rowPinning only changes the internal initial atom value; table.options.state.rowPinning still defaults to empty top/bottom arrays and controls the first render. Set top: ['0'], bottom: ['1'] in createTableState<RowPinningState> for this controlled example.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/svelte/row-pinning/src/App.svelte` around lines 92 - 95, Update the
controlled row-pinning state in createTableState<RowPinningState> to initialize
top with ['0'] and bottom with ['1']; do not rely on initialState.rowPinning,
since table.options.state.rowPinning controls the first render.
| onAfterUpdate: skipFirstRun(() => { | ||
| table_autoResetExpanded(table) | ||
| table_autoResetPageIndex(table) | ||
| table_autoResetSorting(table) | ||
| table_autoResetCellSelection(table) | ||
| }, | ||
| }), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Avoid duplicate expanded-state reset requests for one data update.
When grouping is enabled, a data replacement updates both the core row model and the pre-grouped row model. This callback and createGroupedRowModel then each call table_autoResetExpanded. A controlled onExpandedChange consumer can receive two reset updaters for one data update.
Ensure that one data update schedules one expanded-state reset. Keep grouped resets for grouping or filtering changes that do not already reset in the core stage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/table-core/src/core/row-models/createCoreRowModel.ts` around lines
31 - 36, Update the onAfterUpdate callback in createCoreRowModel so data updates
no longer request table_autoResetExpanded when the grouped-row-model path will
also reset it. Preserve table_autoResetExpanded in createGroupedRowModel for
grouping or filtering changes that are not already reset by the core stage,
while keeping the other core reset calls unchanged.
| /** The first applied result is not a change; auto-resets skip it. */ | ||
| hasAppliedResults: boolean |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reset first-result tracking when the worker terminates.
terminate() retains the bridge in _bridges, but it does not reset hasAppliedResults. When the same table recreates its worker, the first new result is treated as a later result and can reset pagination and expanded state.
Set bridge.hasAppliedResults = false in the terminate loop.
Proposed fix
bridge.sentState = null
bridge.sentStageCount = 0
+ bridge.hasAppliedResults = false
}Also applies to: 162-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/table-core/src/worker/createTableWorker.ts` around lines 75 - 76,
Update the worker termination loop in terminate() to reset hasAppliedResults to
false for each retained bridge before or alongside terminating it, ensuring the
recreated worker treats its first result as the initial result.
| // reset expansion). Like the sync models, the first applied result is not | ||
| // a change and must not fire auto-resets. | ||
| const isFirstAppliedResult = !bridge.hasAppliedResults | ||
| bridge.hasAppliedResults = true | ||
| if (anyChanged && !isFirstAppliedResult) { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Reset expansion for core-stage data changes.
When a worker table uses expansion without grouping, groupedChanged is always false. A data replacement changes the core stage and passes anyChanged, but Lines 263-265 do not reset expanded state. This differs from createCoreRowModel.
Track core-stage changes. Reset expanded state when either the core stage or grouped stage changes.
Proposed fix
let anyChanged = false
+ let coreChanged = false
let groupedChanged = false
for (const [stage, payload] of Object.entries(message.stages) as Array<
@@
bridge.stageVersions[stage] = (bridge.stageVersions[stage] ?? 0) + 1
anyChanged = true
+ if (stage === 'core') coreChanged = true
if (stage === 'grouped') groupedChanged = true
@@
- if (groupedChanged) {
+ if (coreChanged || groupedChanged) {
table_autoResetExpanded(table)
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/table-core/src/worker/createTableWorker.ts` around lines 256 - 260,
Update the change tracking in the worker result application flow around
isFirstAppliedResult so core-stage changes are tracked separately from
grouped-stage changes. Ensure expanded state is reset when either the core stage
or grouped stage changes, while preserving the first-applied-result guard and
existing auto-reset behavior.
🎯 Changes
Auto-resets are
onAfterUpdatehooks on the row model stage memos. Two related defects made them fire in the wrong places.Expansion auto-reset was wired to the wrong stage (#5801)
table_autoResetExpandedwas only called fromcreateGroupedRowModel, soautoResetExpandedsilently did nothing for tables that use expansion without grouping. It is now wired fromcreateCoreRowModelalongside the existing pageIndex, sorting, and cell-selection resets, so a data reference change resets expansion regardless of which features are installed.table_autoResetExpandedgained the sameif (!table.atoms.expanded) returnguard that the sorting and cell-selection resets already use, since the core row model also runs on tables without the expanding feature.Auto-resets fired on first render (#5968)
memoseeds its dependency list to[], so the first computation of every stage always compares as changed. Merely reading a row model on mount scheduled resets for pageIndex, sorting, cell selection, and (for grouped tables) expansion.For uncontrolled tables this was self-cancelling, since the reset targets
initialState— which is why it was hard to reproduce and why the examples all looked fine. It was visible in two cases:initialState.pagination.pageIndexwas wiped to 0 on mountonExpandedChange/onPaginationChangecalls on mount, clobbering state restored from a URL or storagev7 and v8 suppressed the first run with a
registeredflag inside each feature'screateTableclosure. The v9 rewrite dropped it. A newskipFirstRunutil restores that suppression:It is applied in the row model factories, which already run once per table, so each table instance gets its own flag. The grouped row model already tracked its previous inputs and only needed its first-run condition inverted. The worker bridge reports every stage as changed in its first response, so it carries an equivalent
hasAppliedResultsflag.Landing order
These ship together deliberately. Wiring expansion into the core row model without the first-run guard would extend the mount-time wipe from grouped tables to every table using expansion — which is why #6443 was held back. This PR supersedes that one and includes an equivalent regression test.
Changes
table_autoResetExpandedintocreateCoreRowModel(autoResetExpanded not reset the expanded state on data change in V8 #5801)skipFirstRunutil; apply to the core, filtered, and sorted row models (Invokingtable.getRowModel()causes rows to collapse #5968)table_autoResetExpandedwhen the expanding feature is absenttable_autoResetPageIndexcontinues to reset to page 0 rather thaninitialState.pageIndex; that semantic is unchanged and now pinned by test.Examples
Bundled from parallel work on the same tree:
createColumnHelperacross all 10 framework adaptersfilterFns.betweenworkaround in the React expanding example with thefilterFn_betweenindividual export, which already existedVerification
pnpm test:pr: eslint, sherif, knip, test:lib, test:types, test:build, build across 405 projects passedbetaand re-verified✅ Checklist
pnpm test:pr.Closes #5801
Closes #5968
Supersedes #6443
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation