Skip to content

fix: preserve scrollToIndex preload range on initial render - #9769

Draft
vursen wants to merge 7 commits into
mainfrom
fix/preserve-scroll-to-index-preload-on-attach
Draft

fix: preserve scrollToIndex preload range on initial render#9769
vursen wants to merge 7 commits into
mainfrom
fix/preserve-scroll-to-index-preload-on-attach

Conversation

@vursen

@vursen vursen commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

When scrollToIndex or scrollToItem was called in the same roundtrip that attached the grid (for example, in a view constructor), the preloaded viewport range was overwritten back to the first page by GridArrayUpdater.initialize() during the first data flush. The client then had to fetch the target rows in an extra request, briefly showing empty rows.

add(grid); // grid with 1000 items
grid.scrollToIndex(500);

The PR removes the viewport range reset from GridArrayUpdater.initialize(), both to fix this issue and because it was redundant: the Grid constructor already ensures the initial viewport range during the setPageSize call

The viewport range set by scrollToIndex was reset back to the first page by the grid's ArrayUpdater.initialize() during the first data flush, which runs after all user code in the roundtrip. The client then had to fetch the target rows in an extra request, briefly showing empty rows. Reset the range in onAttach instead, and skip the reset when a scroll is pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vursen
vursen force-pushed the fix/preserve-scroll-to-index-preload-on-attach branch from f6ffe32 to 10d30b0 Compare July 21, 2026 09:26
vursen and others added 2 commits July 21, 2026 13:50
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Node attach listeners run before onAttach, so the range preloaded by scrollToIndex and scrollToItem was overwritten by the viewport reset in onAttach. Component attach listeners run after onAttach, so the preload is deferred with those instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vursen
vursen requested a review from tomivirkki July 21, 2026 10:14
@vursen

vursen commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

/code-review

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the changes — left 3 comments.

Finding
⚠️ onAttach now unconditionally resets the viewport range, wiping a preserved scroll range when a grid is moved within one roundtrip.
⚠️ When called before attach, scrollToEnd() loses to an earlier scrollToIndex/scrollToItem, breaking "last scroll wins".
🧹 The 3-line comment explaining runWhenComponentAttached is copy-pasted verbatim in scrollToIndex and scrollToItem.

@vursen
vursen removed the request for review from tomivirkki July 21, 2026 10:47
@vursen
vursen marked this pull request as draft July 21, 2026 10:49
onAttach re-runs the scroll preload after its viewport range reset, so the preload set before attach is no longer overwritten and deferring it until attach is unnecessary.
@vursen
vursen force-pushed the fix/preserve-scroll-to-index-preload-on-attach branch from 704bbcf to d94ceee Compare July 22, 2026 09:52
@vursen
vursen force-pushed the fix/preserve-scroll-to-index-preload-on-attach branch from d94ceee to 92a66a4 Compare July 22, 2026 09:52
@vaadin vaadin deleted a comment from github-actions Bot Jul 22, 2026
@vursen
vursen marked this pull request as ready for review July 22, 2026 09:55
@vursen

vursen commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

/code-review

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the changes — left 2 comments.

Finding
⚠️ On reattach or @PreserveOnRefresh of a scrolled grid, the NO-OP initialize() no longer resets the range, so the fresh client gets a stale scrolled range and does an extra roundtrip.
👀 The ui.add(grid) added to the existing *_preload* tests is cosmetic — without ui.fakeClientCommunication() no flush runs, so these tests still pass against the old buggy code.

@vursen

vursen commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

/code-review

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the changes — left 3 comments.

Finding
⚠️ Moving the viewport-range reset from initialize() to onDetach misses the @PreserveOnRefresh path, where the client re-initializes without a detach.
👀 The new setViewportRange(0, getPageSize()) in onDetach — the actual behavioral addition — has no test.
👀 The emptied initialize() is marked only // NO-OP, with no note on why it must stay empty.

@Override
public void initialize() {
setViewportRange(0, getPageSize());
// NO-OP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ Moving the viewport-range reset from initialize() to onDetach misses the @PreserveOnRefresh path, where the client re-initializes without a detach.

Scroll a Grid inside a @PreserveOnRefresh view (server range becomes e.g. 250-300), then reload the browser. Flow moves the component to the new UI via UIInternalUpdater.moveToNewUI, which calls element.removeFromTree(false). That runs StateNode.reset(false), setting wasAttached=false without firing detach listeners, so Grid.onDetach() — the new home of the reset — never runs. On the following flush isClientSideInitialized() is false, so DataCommunicator calls arrayUpdater.initialize(), which is now a NO-OP, and DataCommunicator.reset() leaves the viewportRange field untouched.

The fresh browser grid starts at scrollTop 0 requesting rows 0-50, but the server still holds the stale 250-300 range and sends those rows first — the exact "extra request, briefly empty rows" symptom this PR set out to remove, now reappearing on refresh. Before the PR, initialize() reset the range to page 0 on this path. Consider resetting the range where re-initialization is detected (or in onAttach guarded so it does not clobber a pre-attach scrollToIndex) rather than only in onDetach.

Grid.java:1469 · correctness · plausible


@Override
protected void onDetach(DetachEvent detachEvent) {
setViewportRange(0, getPageSize());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👀 The new setViewportRange(0, getPageSize()) in onDetach — the actual behavioral addition — has no test.

No test detaches (or detaches and reattaches) a scrolled Grid and asserts the viewport range returns to 0-pageSize. Deleting line 4116 leaves every new and existing test green, so a later change that drops or breaks this reset would ship undetected — a reattached Grid could render from a stale scrolled range instead of the top.

The new *_viewportRangePreloaded tests only cover preservation across attach; add a detach/reattach test to lock in the reset that compensates for the removed initialize() reset.

Grid.java:4116 · test-coverage · plausible

@Override
public void initialize() {
setViewportRange(0, getPageSize());
// NO-OP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👀 The emptied initialize() is marked only // NO-OP, with no note on why it must stay empty.

Flow still calls this override on the first flush after every (re)attach (DataCommunicator line 1204). A developer seeing an empty overridden method with just // NO-OP could reasonably re-add per-flush initialization here, silently reintroducing the range-clobbering bug this PR fixes.

A one-line comment stating that resetting the range here would overwrite a same-roundtrip scrollToIndex preload would prevent that regression.

Grid.java:1469 · maintainability · plausible

@sonarqubecloud

Copy link
Copy Markdown

@vursen
vursen marked this pull request as draft July 22, 2026 12:02
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.

2 participants