Skip to content

fix: table-core's getIsAllSubRowsSelected returns false when subrows are unselectable#6177

Open
kwengontra wants to merge 4 commits intoTanStack:mainfrom
kwengontra:kweng/select-fix
Open

fix: table-core's getIsAllSubRowsSelected returns false when subrows are unselectable#6177
kwengontra wants to merge 4 commits intoTanStack:mainfrom
kwengontra:kweng/select-fix

Conversation

@kwengontra
Copy link

@kwengontra kwengontra commented Feb 21, 2026

🎯 Changes

This PR fixes the root cause for #5173.

The bug was in table-core's getIsAllSubRowsSelected which returned true when all sub-rows are unselectable.
This PR fixes the bug by re-implementing the method using counts: selectedCount == selectableCount && selectedCount > 0
The count is computed with a breadth-first-search traversal rather than the existing recursive approach.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.
    • NX Successfully ran targets test:sherif, test:knip, test:docs, test:lib, test:types, build for 99 projects and 1 task they depend on (2m)

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • Bug Fixes

    • Row selection now correctly ignores unselectable sub-rows when reporting whether all or some sub-rows are selected.
  • New Features

    • Added a counting helper to accurately report selectable and selected sub-rows across nested hierarchies.
  • Tests

    • Expanded test coverage for selection counting and all/some selection behaviors, including nested scenarios and non-selectable sub-rows.

@changeset-bot
Copy link

changeset-bot bot commented Feb 21, 2026

🦋 Changeset detected

Latest commit: c4bd664

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@tanstack/table-core Patch
@tanstack/angular-table Patch
@tanstack/lit-table Patch
@tanstack/qwik-table Patch
@tanstack/react-table Patch
@tanstack/solid-table Patch
@tanstack/svelte-table Patch
@tanstack/vue-table Patch
@tanstack/react-table-devtools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

Refactors row-selection aggregation to count only selectable sub-rows via a new BFS helper (getSubRowSelectionsCount), updates related getters (getIsSomeSelected, getIsAllSubRowsSelected, isSubRowSelected), adds tests covering selectable/non-selectable and nested sub-row scenarios, and adds a changeset patch bump for @tanstack/table-core.

Changes

Cohort / File(s) Summary
Version & Changelog
./.changeset/cute-walls-judge.md
Adds a patch-level version bump and changelog entry noting a bugfix: row.getIsAllSubRowsSelected should return false when subrows are unselectable.
Row Selection Implementation
packages/table-core/src/features/RowSelection.ts
Replaces recursive aggregation with getSubRowSelectionsCount (BFS counting selectable vs selected sub-rows). Updates getIsSomeSelected, getIsAllSubRowsSelected, and isSubRowSelected to use the new counts; adjusts internal call sites and JSDoc wording to “selectable sub rows.”
Row Selection Tests
packages/table-core/tests/RowSelection.test.ts
Adds extensive tests for getSubRowSelectionsCount (selectableCount and selectedCount) and expands coverage for getIsSomeSelected and getIsAllSubRowsSelected across no-subrows, non-selectable, mixed, and nested scenarios with various selection states.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I counted hops in rows below,

BFS footprints in tidy rows.
Selectable friends I sort and see,
No more false "all" — that's jubilee.
A little rabbit's patch and glee. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main bug fix: correcting getIsAllSubRowsSelected to return false when subrows are unselectable.
Description check ✅ Passed The description follows the repository template with all required sections completed: changes explained, checklist fully checked with test results, and release impact properly documented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/table-core/tests/RowSelection.test.ts (1)

464-597: ⚠️ Potential issue | 🟡 Minor

Missing test: getIsAllSubRowsSelected when some sub-rows are non-selectable but all selectable ones are selected.

This is the exact scenario from issue #5173 — the current tests cover "all non-selectable → false" and "all selectable & selected → true", but not the mixed case where the selectable subset is fully selected while non-selectable rows exist. Consider adding:

it('should return true when all selectable sub-rows are selected and some are non-selectable', () => {
  const data = makeData(1, 3)
  const columns = generateColumns(data)

  const table = createTable<Person>({
    enableRowSelection: (row) => row.id !== '0.1', // middle sub-row non-selectable
    onStateChange() {},
    renderFallbackValue: '',
    data,
    getSubRows: (row) => row.subRows,
    state: { rowSelection: { '0.0': true, '0.2': true } },
    columns,
    getCoreRowModel: getCoreRowModel(),
  })

  const firstRow = table.getCoreRowModel().rows[0]
  expect(firstRow.getIsAllSubRowsSelected()).toBe(true)
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/table-core/tests/RowSelection.test.ts` around lines 464 - 597, Add a
test covering the mixed selectable/non-selectable sub-rows case: create data
with three sub-rows via makeData(1, 3), build a table using createTable with
getSubRows and enableRowSelection configured so one sub-row (e.g., id '0.1') is
non-selectable, set state.rowSelection to mark only the selectable sub-rows
(e.g., '0.0' and '0.2') as true, then call
table.getCoreRowModel().rows[0].getIsAllSubRowsSelected() and assert it returns
true; this uses the same helpers from the file (makeData, generateColumns,
createTable, getSubRows, enableRowSelection and getIsAllSubRowsSelected) and
mirrors the provided example in the review comment.
🧹 Nitpick comments (2)
packages/table-core/src/features/RowSelection.ts (2)

647-659: Minor: trailing semicolons after function declarations are inconsistent with the rest of the file.

Lines 649 and 659 have trailing ; after the closing } of export function declarations, which doesn't match the style used elsewhere in this file.

🧹 Suggested fix
 export function getSelectableSubRowCount<TData extends RowData>(row: Row<TData>): number {
   return countSubRows(row, (node) => node.getCanSelect());
-};
+}
 
 /**
  * ...
  */
 export function getSelectedSubRowCount<TData extends RowData>(row: Row<TData>): number {
   return countSubRows(row, (node) => node.getCanSelect() && node.getIsSelected());
-};
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/table-core/src/features/RowSelection.ts` around lines 647 - 659,
Remove the stray trailing semicolons after the two exported function
declarations to match file style: delete the semicolons following the closing
braces of getSelectableSubRowCount and getSelectedSubRowCount so the export
function declarations end with just "}" rather than "};".

505-514: Double traversal: each call to getIsSomeSelected / getIsAllSubRowsSelected performs two full BFS traversals.

Both methods call getSelectableSubRowCount and getSelectedSubRowCount independently, each traversing the entire sub-tree. A single pass returning both counts would cut the work in half.

Additionally, Array.shift() is O(n) due to reindexing — using an index pointer avoids this.

♻️ Suggested combined traversal
+function countSubRowSelections<TData extends RowData>(
+  row: Row<TData>,
+): { selectable: number; selected: number } {
+  const q = [...row.subRows]
+  let selectable = 0
+  let selected = 0
+  let i = 0
+  while (i < q.length) {
+    const node = q[i++]!
+    if (node.getCanSelect()) {
+      selectable++
+      if (node.getIsSelected()) {
+        selected++
+      }
+    }
+    for (const child of node.subRows) {
+      q.push(child)
+    }
+  }
+  return { selectable, selected }
+}

Then in the call sites:

 row.getIsSomeSelected = () => {
-  const selectable = getSelectableSubRowCount(row)
-  const selected = getSelectedSubRowCount(row)
+  const { selectable, selected } = countSubRowSelections(row)
   return selected > 0 && selected < selectable;
 }

 row.getIsAllSubRowsSelected = () => {
-  const selectable = getSelectableSubRowCount(row)
-  const selected = getSelectedSubRowCount(row)
+  const { selectable, selected } = countSubRowSelections(row)
   return selected > 0 && selected === selectable;
 }

Also applies to: 668-679

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/table-core/src/features/RowSelection.ts` around lines 505 - 514, The
two methods row.getIsSomeSelected and row.getIsAllSubRowsSelected currently call
getSelectableSubRowCount and getSelectedSubRowCount separately causing two full
BFS traversals; replace them with a single combined traversal helper that
returns both selectable and selected counts in one pass and have both methods
use that result, and in the traversal avoid Array.shift() by using a numeric
index pointer over the queue/array to iterate (this pattern should also be
applied to the similar logic later in the file that mirrors lines 668-679).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/table-core/tests/RowSelection.test.ts`:
- Around line 150-153: The inline comment claiming getSelectableSubRowCount and
getSelectedSubRowCount are not part of the public interface is wrong; update or
remove that comment in the test file so it accurately reflects that both
functions are exported from RowSelection.ts and are part of the public API
(referencing getSelectableSubRowCount and getSelectedSubRowCount); if you need
to keep explanatory text, reword it to acknowledge these helpers are exported
and clarify why the two RowSelectionRow.* test suites are more useful rather
than asserting they are not exposed.
- Around line 298-299: The inline comment on the createTable<Person> call is
misleading: the predicate enableRowSelection: (row) => row.id !== '0.1' actually
makes the row with id '0.1' non-selectable, not selectable; update the inline
comment near the enableRowSelection usage (next to row.id !== '0.1') to state
that '0.1' is the non-selectable sub-row (or invert the predicate if you
intended the opposite).

---

Outside diff comments:
In `@packages/table-core/tests/RowSelection.test.ts`:
- Around line 464-597: Add a test covering the mixed selectable/non-selectable
sub-rows case: create data with three sub-rows via makeData(1, 3), build a table
using createTable with getSubRows and enableRowSelection configured so one
sub-row (e.g., id '0.1') is non-selectable, set state.rowSelection to mark only
the selectable sub-rows (e.g., '0.0' and '0.2') as true, then call
table.getCoreRowModel().rows[0].getIsAllSubRowsSelected() and assert it returns
true; this uses the same helpers from the file (makeData, generateColumns,
createTable, getSubRows, enableRowSelection and getIsAllSubRowsSelected) and
mirrors the provided example in the review comment.

---

Nitpick comments:
In `@packages/table-core/src/features/RowSelection.ts`:
- Around line 647-659: Remove the stray trailing semicolons after the two
exported function declarations to match file style: delete the semicolons
following the closing braces of getSelectableSubRowCount and
getSelectedSubRowCount so the export function declarations end with just "}"
rather than "};".
- Around line 505-514: The two methods row.getIsSomeSelected and
row.getIsAllSubRowsSelected currently call getSelectableSubRowCount and
getSelectedSubRowCount separately causing two full BFS traversals; replace them
with a single combined traversal helper that returns both selectable and
selected counts in one pass and have both methods use that result, and in the
traversal avoid Array.shift() by using a numeric index pointer over the
queue/array to iterate (this pattern should also be applied to the similar logic
later in the file that mirrors lines 668-679).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/table-core/tests/RowSelection.test.ts`:
- Around line 724-769: Add a positive regression test that verifies
getIsAllSubRowsSelected returns true when some sub-rows are non-selectable but
all selectable sub-rows are selected: create hierarchical data with
makeData(1,2,2) (or similar), configure createTable with
enableRowSelection:true, getSubRows:(r)=>r.subRows, and a state.rowSelection
that marks only the selectable sub-rows (e.g., '0.0', '0.0.0', '0.0.1', '0.1',
'0.1.0', '0.1.1') as true; then call table.getCoreRowModel().rows[0] and assert
firstRow.getIsAllSubRowsSelected() === true to exercise getSubRowSelectionsCount
path and cover the regression.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/table-core/tests/RowSelection.test.ts (1)

326-343: Optional: Extract a table-creation helper to reduce boilerplate.

Many tests repeat the same createTable call differing only in enableRowSelection, data/makeData args, getSubRows, and state.rowSelection. A small factory helper (e.g., createTestTable(opts) with sensible defaults) would cut ~10 lines per test and make the distinguishing parameters more prominent.

This is purely a readability nit — the existing style in the file is consistent, so feel free to defer.

Also applies to: 365-383, 407-424, 446-464

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/table-core/tests/RowSelection.test.ts` around lines 326 - 343,
Extract a small helper (e.g., createTestTable) to reduce repeated createTable
boilerplate across tests that call createTable with similar defaults
(createTable, enableRowSelection, onStateChange, renderFallbackValue,
getCoreRowModel) and varying inputs (data from makeData, columns from
generateColumns, getSubRows, and state.rowSelection); update the tests that
currently call createTable and then use table.getCoreRowModel() /
RowSelection.getSubRowSelectionsCount(firstRow) to call the helper instead,
passing only the differing options so each test only specifies its unique args
(data/getSubRows/state) while the helper supplies the common defaults and
returns the created table.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/table-core/tests/RowSelection.test.ts`:
- Around line 326-343: Extract a small helper (e.g., createTestTable) to reduce
repeated createTable boilerplate across tests that call createTable with similar
defaults (createTable, enableRowSelection, onStateChange, renderFallbackValue,
getCoreRowModel) and varying inputs (data from makeData, columns from
generateColumns, getSubRows, and state.rowSelection); update the tests that
currently call createTable and then use table.getCoreRowModel() /
RowSelection.getSubRowSelectionsCount(firstRow) to call the helper instead,
passing only the differing options so each test only specifies its unique args
(data/getSubRows/state) while the helper supplies the common defaults and
returns the created table.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant