Skip to content

fix: propagate dataview row selection state, add example - #869

Open
rohanchkrabrty wants to merge 3 commits into
mainfrom
dataview-row
Open

fix: propagate dataview row selection state, add example#869
rohanchkrabrty wants to merge 3 commits into
mainfrom
dataview-row

Conversation

@rohanchkrabrty

Copy link
Copy Markdown
Contributor

Summary

  • Fix row selection never reaching useDataView() consumers: the context value is memoized over deps a selection change doesn't touch and the TanStack table instance identity is stable, so row.toggleSelected() re-rendered the root but produced an identical context value — checkboxes only caught up when a filter edit or consumer re-render invalidated the memo. rowSelection is now root state alongside columnVisibility.
  • Backfill flatRows on the filtered row model. DataView filters with filterFromLeafRows so groups survive when a child matches, and table-core's leaf-first implementation allocates that array but never fills it, leaving getIsAllRowsSelected(), getIsSomeRowsSelected() and toggleAllRowsSelected() dead whenever a filter or search was active.
  • Add an onRowSelectionChange prop on the root for mirroring selection outside the tree, and expose rowSelection / setRowSelection on the context.
  • Add a Row selection example to the DataView docs, mirroring DataTable's: an unmanaged select column (absent from fields, so it stays out of Display Properties and can't be filtered, sorted, or grouped) plus a FloatingActions bar reading selection from useDataView().
  • Document the sharp edges: when getRowId actually matters (positional keys survive client sort/filter/search but not a data reorder or a grouping switch), why TanStack's getToggleSelectedHandler / getToggleAllRowsSelectedHandler don't fit a Base UI checkbox, and that grouping plus selection is still unsupported because group header rows share the row-id space.

rohanchkrabrty and others added 2 commits July 30, 2026 16:10
Row selection changes never reached `useDataView()` consumers. The context
value is memoized over deps that a selection change doesn't touch, and the
TanStack table instance identity is stable, so `row.toggleSelected()`
re-rendered the root but produced an identical context value. Checkboxes
only caught up when something else invalidated the memo — a filter edit or a
consumer re-render.

Lift `rowSelection` into root state alongside `columnVisibility` so it is a
real dependency of the context value, expose it on the context, and add an
`onRowSelectionChange` prop for mirroring selection outside the tree.

Also backfill `flatRows` on the filtered row model. DataView filters with
`filterFromLeafRows` so groups survive when a child matches, and
table-core's leaf-first implementation allocates that array but never pushes
into it — leaving `table.getIsAllRowsSelected()`, `getIsSomeRowsSelected()`
and `toggleAllRowsSelected()` dead whenever a filter or search was active.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mirrors DataTable's pattern: an unmanaged `select` column (absent from
`fields`, so it stays out of Display Properties and can't be filtered,
sorted, or grouped) plus a FloatingActions bar reading selection from
`useDataView()`.

Notes cover what the docs example can't show inline: why `getRowId` matters
(positional keys survive client sort/filter/search but not a data reorder or
a grouping switch), why TanStack's `getToggleSelectedHandler` /
`getToggleAllRowsSelectedHandler` don't fit a Base UI checkbox, and that
grouping plus selection is still unsupported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview Jul 30, 2026 11:00am

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@rohanchkrabrty, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c59e7861-9665-4b24-9804-33a18994fff2

📥 Commits

Reviewing files that changed from the base of the PR and between d4afb40 and 7baa7ef.

📒 Files selected for processing (5)
  • apps/www/src/components/dataview-demo.tsx
  • apps/www/src/content/docs/components/dataview/demo.ts
  • apps/www/src/content/docs/components/dataview/index.mdx
  • packages/raystack/components/data-view/data-view.tsx
  • packages/raystack/components/data-view/utils/index.tsx
📝 Walkthrough

Walkthrough

DataView now controls TanStack row selection state, exposes it through useDataView, and reports changes through onRowSelectionChange. A filtered row-model utility ensures filtered rows retain populated flatRows. New application and documentation demos add checkbox selection columns, selection-aware floating actions, stable row IDs, and selection reset behavior. The DataView documentation describes selection APIs, event handling, positioning, and grouping limitations.

Sequence Diagram(s)

sequenceDiagram
  participant CheckboxColumn
  participant DataView
  participant TanStackTable
  participant SelectionBar

  CheckboxColumn->>TanStackTable: Toggle row selection
  TanStackTable->>DataView: Emit selection updater
  DataView->>DataView: Resolve controlled rowSelection state
  DataView->>SelectionBar: Expose selected rows through useDataView
  SelectionBar->>DataView: Reset selection on dismiss
Loading

Suggested reviewers: paansinghcoder, rohilsurana, ravisuhag

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: propagating DataView row selection state and adding an example.
Description check ✅ Passed The description is directly related to the changeset and matches the implemented selection-state, filtering, and docs updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/www/src/content/docs/components/dataview/demo.ts`:
- Around line 343-350: Update the import list in the demo snippet to include the
DataViewListColumn type from `@raystack/apsara`, so the selectionColumn
declaration resolves correctly when the example is copied and type-checked.

In `@packages/raystack/components/data-view/data-view.tsx`:
- Around line 141-147: Update handleRowSelectionChange so the
setRowSelectionState updater only computes and returns the next selection state;
move consumer notification out of the updater by tracking the latest reported
selection and invoking onRowSelectionChange from an effect after the state
commit, while avoiding duplicate reports.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: cc082ee8-0417-48e4-b7b2-7068bd3811d9

📥 Commits

Reviewing files that changed from the base of the PR and between 5e38ff6 and d4afb40.

📒 Files selected for processing (8)
  • apps/www/src/components/dataview-demo.tsx
  • apps/www/src/components/demo/demo.tsx
  • apps/www/src/content/docs/components/dataview/demo.ts
  • apps/www/src/content/docs/components/dataview/index.mdx
  • apps/www/src/content/docs/components/dataview/props.ts
  • packages/raystack/components/data-view/data-view.tsx
  • packages/raystack/components/data-view/data-view.types.tsx
  • packages/raystack/components/data-view/utils/index.tsx

Comment on lines +343 to +350
code: `import {
Button,
Checkbox,
Chip,
DataView,
FloatingActions,
useDataView,
} from "@raystack/apsara";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 'DataViewListColumn<Person>|DataViewListColumn' \
  apps/www/src/content/docs/components/dataview/demo.ts \
  packages/raystack/components/data-view/index.ts

Repository: raystack/apsara

Length of output: 1092


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== demo snippet =="
sed -n '330,370p' apps/www/src/content/docs/components/dataview/demo.ts

echo
echo "== data-view index exports =="
sed -n '1,40p' packages/raystack/components/data-view/index.ts

echo
echo "== occurrences of DataViewListColumn import =="
rg -n 'Import\s*<\s*.*DataViewListColumn|dataViewListColumn|DataViewListColumn' apps/www/src/content docs packages --glob '!**/node_modules/**' | head -200

Repository: raystack/apsara

Length of output: 4806


Import DataViewListColumn in the example.

selectionColumn is typed as DataViewListColumn<Person>, but that type is not imported from @raystack/apsara, so copied TSX fails type checking.

Proposed fix
 import {
   Button,
   Checkbox,
   Chip,
   DataView,
+  type DataViewListColumn,
   FloatingActions,
   useDataView,
 } from "`@raystack/apsara`";
📝 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
code: `import {
Button,
Checkbox,
Chip,
DataView,
FloatingActions,
useDataView,
} from "@raystack/apsara";
code: `import {
Button,
Checkbox,
Chip,
DataView,
type DataViewListColumn,
FloatingActions,
useDataView,
} from "`@raystack/apsara`";
🤖 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 `@apps/www/src/content/docs/components/dataview/demo.ts` around lines 343 -
350, Update the import list in the demo snippet to include the
DataViewListColumn type from `@raystack/apsara`, so the selectionColumn
declaration resolves correctly when the example is copied and type-checked.

Comment on lines +141 to +147
const handleRowSelectionChange = useCallback(
(value: Updater<RowSelectionState>) => {
setRowSelectionState(prev => {
const newValue = typeof value === 'function' ? value(prev) : value;
onRowSelectionChange?.(newValue);
return newValue;
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
rg -n -C3 'onRowSelectionChange|StrictMode|toggleSelected|setRowSelection' \
  packages/raystack/components/data-view/__tests__/data-view.test.tsx

Repository: raystack/apsara

Length of output: 153


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== locate files =="
fd -a 'data-view\.tsx$|data-view\.test\.tsx$' . | sed 's#^\./##' | head -50

echo "== outline target =="
ast-grep outline packages/raystack/components/data-view/data-view.tsx || true

echo "== relevant source lines =="
cat -n packages/raystack/components/data-view/data-view.tsx | sed -n '1,240p'

echo "== tests relevant mentions =="
python3 - <<'PY'
from pathlib import Path
p = Path('packages/raystack/components/data-view/__tests__/data-view.test.tsx')
if p.exists():
    txt = p.read_text()
    for term in ['onRowSelectionChange','StrictMode','toggleSelected','setRowSelection','selection']:
        print(f'\\n-- {term} --')
        try:
            hits = list(p.rglob('*'))  # only one file so rg would work; search text here
            for i,line in enumerate(txt.splitlines(),1):
                if term.lower() in line.lower():
                    print(f'{i}: {line}')
        except Exception as e:
            print('ERR', e)
else:
    print('test file missing')
PY

Repository: raystack/apsara

Length of output: 9993


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import subprocess, tempfile, os, re

src = subprocess.check_output(['git','show','HEAD:packages/raystack/components/data-view/data-view.tsx'], text=True)
lines = src.splitlines()
selected = 89 if selected == 89 else None

# Show relevant later lines
for i,line in enumerate(lines[240:360], 241):
    if i>330: break
    print(f"{i}: {line}")

# Extract hook body and dependency usage patterns
for name in ['useCallback','useEffect','useRef','useState','useReactTable','rootProps']:
    print(f"\n-- occurrences of {name} --")
    for i,l in enumerate(lines,1):
        if re.search(rf"\b{name}\b", l):
            print(f"{i}: {l}")
PY

echo "== data-view test selection snippets =="
python3 - <<'PY'
from pathlib import Path
p = Path('packages/raystack/components/data-view/__tests__/data-view.test.tsx')
text = p.read_text()
for i,line in enumerate(text.splitlines(),1):
    if i >= 1120 and i <= 1205:
        print(f"{i}: {line}")
PY

Repository: raystack/apsara

Length of output: 268


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== later DataViewRoot/source context =="
cat -n packages/raystack/components/data-view/data-view.tsx | sed -n '241,380p'

echo "== hook/context usages =="
rg -n -C2 'useCallback|useEffect|useRef|useState|useReactTable|onRowSelectionChange|onColumnVisibilityChange' packages/raystack/components/data-view/data-view.tsx packages/raystack/components/data-view/__tests__/data-view.test.tsx

echo "== selection-related test snippets =="
cat -n packages/raystack/components/data-view/__tests__/data-view.test.tsx | sed -n '1128,1205p'

echo "== package react versions from lock/package data =="
python3 - <<'PY'
from pathlib import Path
for p in [Path('package.json'), Path('pnpm-lock.yaml'), Path('yarn.lock')]:
    if p.exists():
        print(f"\n-- {p} --")
        text = p.read_text(errors='ignore')
        for needle in ['"react"', '"react-dom"', '`@types/react`']:
            if needle in text:
                for line in text.splitlines():
                    if needle in line:
                        print(line)
PY

Repository: raystack/apsara

Length of output: 50371


Move onRowSelectionChange out of the state updater.

Calling a consumer callback inside the setRowSelectionState(prev => ...) function lets it run during renders or React 18/19 Strict Mode double-invocation paths, so consumers can receive duplicate or out-of-order selection updates. Track the last reported selection and report from an effect after the commit/update path.

Proposed fix
 const [rowSelection, setRowSelectionState] = useState<RowSelectionState>({});
+const lastReportedRowSelection = useRef(rowSelection);

 const handleRowSelectionChange = useCallback(
   (value: Updater<RowSelectionState>) => {
     setRowSelectionState(prev => {
       const newValue = typeof value === 'function' ? value(prev) : value;
-      onRowSelectionChange?.(newValue);
       return newValue;
     });
   },
-  [onRowSelectionChange]
+  []
 );
+
+useEffect(() => {
+  if (lastReportedRowSelection.current === rowSelection) return;
+  lastReportedRowSelection.current = rowSelection;
+  onRowSelectionChange?.(rowSelection);
+}, [rowSelection, onRowSelectionChange]);
📝 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 handleRowSelectionChange = useCallback(
(value: Updater<RowSelectionState>) => {
setRowSelectionState(prev => {
const newValue = typeof value === 'function' ? value(prev) : value;
onRowSelectionChange?.(newValue);
return newValue;
});
const handleRowSelectionChange = useCallback(
(value: Updater<RowSelectionState>) => {
setRowSelectionState(prev => {
const newValue = typeof value === 'function' ? value(prev) : value;
return newValue;
});
},
[]
);
useEffect(() => {
if (lastReportedRowSelection.current === rowSelection) return;
lastReportedRowSelection.current = rowSelection;
onRowSelectionChange?.(rowSelection);
}, [rowSelection, onRowSelectionChange]);
🤖 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/raystack/components/data-view/data-view.tsx` around lines 141 - 147,
Update handleRowSelectionChange so the setRowSelectionState updater only
computes and returns the next selection state; move consumer notification out of
the updater by tracking the latest reported selection and invoking
onRowSelectionChange from an effect after the state commit, while avoiding
duplicate reports.

Group rows are synthesized by `groupData` and carry no consumer id, so
`getRowId` returned undefined for them and TanStack fell back to a positional
id — which collided with data row ids. With ids `1,2,3` and grouping on, row
ids came out as `0=GROUP 1=Ada 2=Grace 1=GROUP 3=Susan`, so clicking Ada
resolved to the Design group row and cascaded selection to Susan. React also
warned about duplicate keys.

Buckets are now keyed `__group__:<group_key>`, leaves delegate to `getRowId`,
and without one we reproduce TanStack's positional default (`'0'`, and
`'<parent>.<index>'` for sub-rows) since supplying a resolver replaces it.
Group rows are also unselectable, so `rowSelection` holds one key per data
row and select-all doesn't flag the bands.

Grouping and selection now compose: the docs example drops `hideGrouping`,
counts off `getSelectedRowModel().flatRows` (`.rows` only walks selected
top-level rows, which is empty when grouping pushes data a level down), and
guards `indeterminate` with `!getIsAllRowsSelected()`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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