Skip to content

Fix InitialItemIndex is intermittently ignored on fresh page load - #68114

Open
ilonatommy wants to merge 3 commits into
mainfrom
fix-68099
Open

Fix InitialItemIndex is intermittently ignored on fresh page load #68114
ilonatommy wants to merge 3 commits into
mainfrom
fix-68099

Conversation

@ilonatommy

@ilonatommy ilonatommy commented Jul 30, 2026

Copy link
Copy Markdown
Member

Fixes #68099.

It's not visible with fast connection but with throttling the reproduction is stable, videos for reference.

Before the fix:

InitialItemIndex.-.Dan.race.mp4

After the fix:

InitialItemIndex.-.Dan.race.fixed.mp4

Fix

Pass suppressInitialSpacerCallbacks to JS init method so that JS can skip observer's initial callback that the align-to-item method was racing with.

Why we can do that safely:
We don't need the initial spacer callback because the later callbacks are these that really load the data. So we care only about callbacks that are triggered after ScollToItemAsync finishes its job of placing the viewport in the requested position. The placing is done in alignToItems. alignToItems changes scrollTop, which changes the spacer sizes, which in turn, triggers further spacer callbacks. These callbacks are not supressed anymore, they finish normally. That's how the items are eventually loaded.

Why it fixes the issue:
Removing initial callback removes the race of "load items from index 0" (coming from the initial callback) and "shift the viewport to index Nth" (coming from the IntialItemIndex that calls ScollToItemAsync). Now we have only the latter one.

Why the tests are not in the component we normally used for InitialItemIndex tests:
The test had to be added to a component that contains "to interactivity" transition so I reused the VirtualizeAnchorMode component in VirtualizationTransitionToInteractivity.razor.

@ilonatommy ilonatommy added this to the 11.0-rc1 milestone Jul 30, 2026
@ilonatommy ilonatommy self-assigned this Jul 30, 2026
Copilot AI review requested due to automatic review settings July 30, 2026 14:04
@ilonatommy
ilonatommy requested a review from a team as a code owner July 30, 2026 14:04
@ilonatommy ilonatommy added area-blazor Includes: Blazor, Razor Components feature-blazor-virtualization This issue is related to the Blazor Virtualize component labels Jul 30, 2026

Copilot AI 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.

Pull request overview

Fixes a startup race in the Virtualize<TItem> initial-scroll path where the spacers’ IntersectionObserver initial callback can run before the initial programmatic alignment, intermittently resetting the window back to index 0 on fresh loads (especially with prerendering + interop latency).

Changes:

  • Adds a new suppressInitialSpacerCallbacks flag to the JS Virtualize.init interop call so JS can ignore the observers’ initial callback until the component’s first programmatic-scroll decision is made.
  • Plumbs the new flag from VirtualizeVirtualizeJsInteropVirtualize.ts, enabling suppression when InitialItemIndex > 0.
  • Adds coverage: a unit test verifying the init-argument roundtrip, plus an E2E test that validates InitialItemIndex remains applied after transitioning to interactive server mode.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Components/Web/test/Virtualization/VirtualizeTest.cs Adds a unit test verifying InitialItemIndex controls initial spacer-callback suppression passed to JS init.
src/Components/Web/src/Virtualization/VirtualizeJsInterop.cs Extends InitializeAsync to pass the suppression flag to Blazor._internal.Virtualize.init.
src/Components/Web/src/Virtualization/Virtualize.cs Enables initial suppression when InitialItemIndex > 0 during first render initialization.
src/Components/Web.JS/src/Virtualize.ts Adds suppressInitialSpacerCallbacks param and initializes suppressSpacerCallbacks from it to ignore the initial IO callback.
src/Components/test/testassets/Components.TestServer/RazorComponents/Pages/Interactivity/VirtualizationTransitionToInteractivity.razor Adds a query-switched path to exercise VirtualizationAnchorMode during transition-to-interactivity.
src/Components/test/testassets/BasicTestApp/VirtualizationAnchorMode.razor Makes InitialItemIndex a component parameter and wires it through to the Virtualize component and UI.
src/Components/test/E2ETest/ServerRenderingTests/VirtualizationRenderModesTest.cs Adds an E2E assertion that InitialItemIndex is applied after prerender → interactive server load.

Comment thread src/Components/test/E2ETest/ServerRenderingTests/VirtualizationRenderModesTest.cs Outdated
@@ -45,6 +45,22 @@ public void Virtualize_Works_WhenMultipleRenderModesAreActive()
Browser.True(() => GetRenderedItems(Browser.FindElement(By.Id("virtualize-webassembly"))).Contains("Item 50"));
}

[Fact]
public void InitialItemIndex_IsAppliedOnPrerenderedInteractiveServerLoad()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this only reproduces with network latency, the test could pass even without the fix. For my own education - is it possible to emulate network latency before navigation so the initial observer callback fires first?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch, I was simplifying it to the point where I lost the fail-on-regression point. We can add an artificial gate in JS where we would detect the initial callback and hold it until the alignment signal comes, then release both and delay suppression of programmatic scroll.

@oroztocil oroztocil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks correct but also as a fix for the symptom rather than the cause.

Idea (maybe misguided): add a bool userInitiated argument to OnSpacerBeforeVisible and OnSpacerAfterVisible that passes information if they were initiated do to user scroll or programmatic scroll. The JS side should be able to recognize/track that. ShouldSuppressSpacerCallback can use then this information so it does not have to infer user scroll vs programmatic scroll based on timing (which introduces the race, if I understand things correctly) and the JS suppression added in this PR would not be needed.

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

Labels

area-blazor Includes: Blazor, Razor Components feature-blazor-virtualization This issue is related to the Blazor Virtualize component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Virtualize: InitialItemIndex is intermittently ignored on fresh page load (startup race with spacer IntersectionObserver callback)

4 participants