Skip to content

Comments

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

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

fix: table-core's getIsAllSubRowsSelected returns false when subrows are unselectable#6177
kwengontra wants to merge 2 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

Release Notes

  • Bug Fixes

    • Fixed table row selection to correctly report when all sub-rows are selected, properly accounting for unselectable sub-rows in the calculation.
  • New Features

    • Added new helper functions for counting selectable and selected sub-rows within the row hierarchy.

@changeset-bot
Copy link

changeset-bot bot commented Feb 21, 2026

🦋 Changeset detected

Latest commit: 519e40d

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

A patch version bump for @tanstack/table-core introduces a bugfix to row selection logic by replacing tri-state evaluation with breadth-first search-based counting helpers, ensuring getIsAllSubRowsSelected correctly returns false for unselectable sub-rows.

Changes

Cohort / File(s) Summary
Version & Changelog
.changeset/cute-walls-judge.md
Documents patch-level version bump and changelog entry for the row selection bugfix.
Row Selection Implementation
packages/table-core/src/features/RowSelection.ts
Replaces tri-state logic with BFS-based counting helpers. Adds getSelectableSubRowCount and getSelectedSubRowCount for aggregated sub-row evaluation. Removes exported isSubRowSelected helper. Updates getIsAllSubRowsSelected and getIsSomeSelected to use new counting methods and reference "selectable sub-rows".
Row Selection Tests
packages/table-core/tests/RowSelection.test.ts
Replaces isSubRowSelected test cases with new test suites for getSelectableSubRowCount, getSelectedSubRowCount, getIsSomeSelected, and getIsAllSubRowsSelected. Expands coverage across varying table configurations and nested sub-row scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Hop, skip, and count we go,
BFS through sub-rows, to and fro,
Selectable helpers now arise,
False when hidden meets our eyes,
Row selection logic, debugged and bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 PR title clearly and specifically describes the main bug fix: that getIsAllSubRowsSelected now returns false when subrows are unselectable, which directly matches the core changes in the implementation.
Description check ✅ Passed The PR description follows the required template with all sections completed: detailed explanation of the bug fix and root cause under 🎯 Changes, both checklist items marked as completed with test results provided, and release impact section indicating a changeset was generated.

✏️ 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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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).

Comment on lines +150 to +153

// IMO the following 2 tests suites are not useful as they are not exposed to the public interface
// Instead the 2 RowSelectionRow.* test suites are more useful.
describe('getSelectableSubRowCount', () => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Incorrect comment: getSelectableSubRowCount and getSelectedSubRowCount are part of the public interface.

Both functions are export function in RowSelection.ts, so they are exposed to consumers. The comment on lines 151–152 claims otherwise. Consider removing or correcting it.

🤖 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 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.

Comment on lines +298 to +299
const table = createTable<Person>({
enableRowSelection: (row) => row.id !== '0.1', // second sub-row is selectable
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Misleading comment: '0.1' is the non-selectable sub-row, not the selectable one.

The predicate row.id !== '0.1' makes '0.1' non-selectable. The inline comment says the opposite.

🧹 Suggested fix
-        enableRowSelection: (row) => row.id !== '0.1', // second sub-row is selectable
+        enableRowSelection: (row) => row.id !== '0.1', // second sub-row is NOT selectable
📝 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.

Suggested change
const table = createTable<Person>({
enableRowSelection: (row) => row.id !== '0.1', // second sub-row is selectable
const table = createTable<Person>({
enableRowSelection: (row) => row.id !== '0.1', // second sub-row is NOT selectable
🤖 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 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).

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