Skip to content

Scrolling fix#1208

Merged
piecyk merged 9 commits into
TanStack:mainfrom
dracofulmen:main
Jun 26, 2026
Merged

Scrolling fix#1208
piecyk merged 9 commits into
TanStack:mainfrom
dracofulmen:main

Conversation

@dracofulmen

@dracofulmen dracofulmen commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Added check to observeElementOffset to prevent continuous re-renders

🎯 Changes

Currently, observeElementOffset causes isScrolling to get stuck as true for as long as offset > 0. This causes continuous rerenders even after scrolling stops. The this.maybeNotify(); call also causes continuous rerenders while scrolling. Both of these issues cause the scroll bar to flicker. This fix adds a check to make sure that scrolling only causes notifications once, when the scrolling stops.

✅ Checklist

  • [✅] I have followed the steps in the Contributing guide.
  • [✅] I have tested this code locally with pnpm run test:pr.

🚀 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
    • Fixed a continuous re-render issue during or after scrolling in the virtualized list.
    • Improved scroll offset observation by skipping redundant intended-offset reconciliation when the reported offset already matches the current scroll position during active scrolling.

Added check to observeElementOffset to prevent continuous re-renders
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A guard is added in Virtualizer._willUpdate’s observeElementOffset callback to skip same-offset re-emits during active scrolling, and a patch changeset is added for @tanstack/virtual-core.

Changes

observeElementOffset rerender fix

Layer / File(s) Summary
Scroll offset guard and release note
packages/virtual-core/src/index.ts, .changeset/cold-ads-lose.md
Adds an early-return condition when isScrolling is true and the observed offset matches scrollOffset, and adds a patch changeset entry for @tanstack/virtual-core.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~5 minutes

Suggested reviewers

  • tannerlinsley

Poem

🐇 I hopped beside the scrolling stream,
A matching echo broke the dream.
One tiny guard said, “Hop no more,”
And looped rerenders reached the floor.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the change, but it is too generic to clearly describe the main fix. Use a concise, specific title that names the scrolling re-render fix in observeElementOffset.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the required template and includes the changes, checklist, and release impact sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/virtual-core/src/index.ts`:
- Around line 782-788: The current code in the scroll offset handling block only
calls maybeNotify() when the offset equals the current scrollOffset, but fails
to notify when the offset actually changes (offset !== this.scrollOffset). This
prevents onChange notifications from being emitted during normal scroll updates.
Add a notification call (maybeNotify()) for the case where the offset differs
from this.scrollOffset to ensure virtual range updates are properly triggered.
This fix should be applied to both occurrences mentioned, including the one
around line 814.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 555ddbcd-1cbe-4be9-8450-226ab25e8ff7

📥 Commits

Reviewing files that changed from the base of the PR and between 75ae896 and 4a61b19.

📒 Files selected for processing (2)
  • .changeset/cold-ads-lose.md
  • packages/virtual-core/src/index.ts

Comment thread packages/virtual-core/src/index.ts Outdated

@piecyk piecyk 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.

@dracofulmen Hi, could you clarify what "continuous re-renders" means concretely here, what's measured as re-rendering, and under what interaction?

Basic maybeNotify is memoized on [isScrolling, startIndex, endIndex], so a scroll event at an unchanged offset can't cause a re-render unless isScrolling toggles (the range can't have moved)

@dracofulmen

Copy link
Copy Markdown
Contributor Author

@piecyk I'm using @workday/canvas-kit-react v15.0.14. Here's the video of the issue: Reproduction.mov. And here's the code that I used to reproduce it: minimal.tsx. When I dove into the react devtools, I found that isScrolling gets stuck on true as long as the scroll bar isn't at the top. This is why I added the offset === this.scrollOffset && isScrolling === true check. However, without moving the this.maybeNotify() call, I still got the flashes during scrolling.

@piecyk

piecyk commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

@piecyk I'm using @workday/canvas-kit-react v15.0.14. Here's the video of the issue: Reproduction.mov. And here's the code that I used to reproduce it: minimal.tsx. When I dove into the react devtools, I found that isScrolling gets stuck on true as long as the scroll bar isn't at the top. This is why I added the offset === this.scrollOffset && isScrolling === true check. However, without moving the this.maybeNotify() call, I still got the flashes during scrolling.

Hmm, that looks off. Could you prepare a reproducible example on stackblitz using the minimal example you mentioned, so we can investigate further?

Once we have that, we can dig into why isScrolling remains stuck.

@dracofulmen

Copy link
Copy Markdown
Contributor Author

@piecyk Here's the stackblitz reproduction

@piecyk

piecyk commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

@piecyk Here's the stackblitz reproduction

@dracofulmen thanks! Could you share what environment you’re seeing this issue in?

I tested it on Chrome and Firefox on macOS and it looks good, no flickering on my side.

Also, virtualization is not enabled by default. You need to pass shouldVirtualize: true to useSelectModel to enable it.

@dracofulmen

Copy link
Copy Markdown
Contributor Author

I'm using it in Firefox on Mac.

@piecyk piecyk 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.

Thanks @dracofulmen, the re-render loop is real. I can reproduce it reliably in Firefox, though not in Chrome, with the virtualized Select.

After scrolling stops, it keeps re-rendering indefinitely, roughly every ~168ms, which lines up with isScrollingResetDelay.

Bit tweaked your fix to ignore a scroll event when both of these are true:

  • the event lands on the offset we already have,
  • it is not a self-write read-back.

Real scrolls still change the offset, so they pass through normally. Programmatic scrolls are still handled by the _intendedScrollOffset reconciliation below.

I tested it, and the loop is gone while scrolling still works.

One thing I want to be upfront about: I did not isolate exactly why the browser fires that extra scroll event. I know it is browser-generated and happens right after each render, but I have not pinned down the underlying mechanism.

It would be nice to move @workday/canvas-kit-react to directDomUpdates. That should reduce the number of re-renders while scrolling:
https://tanstack.com/virtual/latest/docs/framework/react/react-virtual#directdomupdates

Feel free to create a PR on their side and ping me for review.

Comment thread packages/virtual-core/src/index.ts Outdated
Comment thread packages/virtual-core/src/index.ts
Comment thread packages/virtual-core/src/index.ts
dracofulmen and others added 3 commits June 25, 2026 13:01
Co-authored-by: Damian Pieczynski <piecyk@gmail.com>
Co-authored-by: Damian Pieczynski <piecyk@gmail.com>
Co-authored-by: Damian Pieczynski <piecyk@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/virtual-core/src/index.ts (1)

775-822: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression test for the new no-op scroll branch.

This is a browser-specific change in a hot path, but the provided test context only covers adjacent contracts (isScrolling deferral and _intendedScrollOffset reconciliation), not the new isScrolling && this._intendedScrollOffset === null && offset === this.scrollOffset early return. A focused unit test here would lock in the Safari/Firefox fix and protect against future regressions in onChange/isScrolling behavior.

🤖 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/virtual-core/src/index.ts` around lines 775 - 822, Add a focused
regression test around the scroll handling in
`packages/virtual-core/src/index.ts` that covers the new early return in the
`onChange`/scroll path when `isScrolling` is true, `_intendedScrollOffset` is
null, and `offset === this.scrollOffset`. Verify this branch does not re-arm
`isScrolling`, does not trigger an extra `maybeNotify`/re-render cycle, and
preserves the existing `_intendedScrollOffset` reconciliation and
`scheduleScrollReconcile` behavior for nearby cases.
🤖 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.

Nitpick comments:
In `@packages/virtual-core/src/index.ts`:
- Around line 775-822: Add a focused regression test around the scroll handling
in `packages/virtual-core/src/index.ts` that covers the new early return in the
`onChange`/scroll path when `isScrolling` is true, `_intendedScrollOffset` is
null, and `offset === this.scrollOffset`. Verify this branch does not re-arm
`isScrolling`, does not trigger an extra `maybeNotify`/re-render cycle, and
preserves the existing `_intendedScrollOffset` reconciliation and
`scheduleScrollReconcile` behavior for nearby cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 798b27a3-9941-48b4-ac08-08b8be164412

📥 Commits

Reviewing files that changed from the base of the PR and between 4a61b19 and e25a986.

📒 Files selected for processing (1)
  • packages/virtual-core/src/index.ts

This prevents hiccups during scrolling itself.
This reverts commit 2b4c600.
@nx-cloud

nx-cloud Bot commented Jun 26, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 6ce9b8c

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 40s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 17s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-26 05:43:32 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-virtual

npm i https://pkg.pr.new/@tanstack/angular-virtual@1208

@tanstack/lit-virtual

npm i https://pkg.pr.new/@tanstack/lit-virtual@1208

@tanstack/react-virtual

npm i https://pkg.pr.new/@tanstack/react-virtual@1208

@tanstack/solid-virtual

npm i https://pkg.pr.new/@tanstack/solid-virtual@1208

@tanstack/svelte-virtual

npm i https://pkg.pr.new/@tanstack/svelte-virtual@1208

@tanstack/virtual-core

npm i https://pkg.pr.new/@tanstack/virtual-core@1208

@tanstack/vue-virtual

npm i https://pkg.pr.new/@tanstack/vue-virtual@1208

commit: d818cd1

@piecyk piecyk merged commit b04f9ee into TanStack:main Jun 26, 2026
10 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 26, 2026
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