Skip to content

DataViews: Fix infinite-scroll list jumping while a page loads - #79334

Closed
StevenDufresne wants to merge 2 commits into
WordPress:trunkfrom
StevenDufresne:fix/dataviews-infinite-scroll-anchor-jump
Closed

StevenDufresne wants to merge 2 commits into
WordPress:trunkfrom
StevenDufresne:fix/dataviews-infinite-scroll-anchor-jump

Conversation

@StevenDufresne

Copy link
Copy Markdown
Contributor

What?

Fixes a bug in DataViews infinite scroll where the list jumps (the scroll position is yanked upward) while you scroll down to load more, when the consumer loads pages asynchronously.

This PR also adds an Async Infinite Scroll story that reproduces the bug in one step.

Why?

The infinite-scroll scroll-anchor restoration is meant to keep the viewport stable when items are inserted/removed (e.g. paging in older items). It works by capturing an anchor element and its viewport offset when the window advances, then re-applying that offset after the next render.

The problem: capture happens inside the throttled scroll handler the instant the window advances, but the restoration runs on a later render (it's gated on isLoading). When pages load asynchronously, the user keeps scrolling between capture and restore. The restoration can't distinguish that user-driven movement from a content-driven shift, so it "corrects" it — snapping the list back to where the anchor sat at capture time and undoing the scrolling the user did while the page was loading.

The existing synchronous InfiniteScroll story never hits this: it passes no isLoading and resolves data synchronously, so the restoration effect never runs and there's no async gap for the user to scroll into. Real consumers that load over the network (a notifications list, for example) hit it consistently.

How?

Using content-position P and scroll S, an element's offset from the container top is P − S.

  • Capture: viewportOffset = P1 − S1
  • Restore: currentOffset = P2 − S2; the old adjustment currentOffset − viewportOffset resolves the scroll position to P2 − P1 + S1 — i.e. it pins the anchor to its capture-time position, discarding the user's scroll (S2 − S1). For a pure scroll-down append P2 = P1, so the adjustment is S1 − S2, exactly negating the user's scroll.

The fix stores scrollTop at capture and adds the intervening scroll delta back:

scrollAdjustment = (currentOffset − viewportOffset) + (scrollTop − capturedScrollTop) = P2 − P1

So only the content-driven shift is compensated:

  • Append below the viewport (scroll down): P2 = P1 → adjustment 0 → no jump.
  • Insert above the viewport (scroll up / prepend): P2 = P1 + H → adjustment H → position correctly held.

When the user does not scroll during the load, scrollTop == capturedScrollTop, the added term is 0, and behaviour is byte-for-byte identical to before — so this only changes the currently-broken case.

Note: scroll anchoring depends on real layout/scroll, which jsdom does not simulate, so this is verified via the story below rather than a unit test.

Testing Instructions

  1. npm run storybook:dev
  2. Open DataViews → DataViews → Async Infinite Scroll.
  3. Scroll down continuously through several pages (each page has ~700ms simulated latency, so keep scrolling while it loads).
  4. With this PR: the list stays put as each page settles.
  5. To see the bug: check out packages/dataviews/src/hooks/use-infinite-scroll.ts from trunk (git checkout trunk -- packages/dataviews/src/hooks/use-infinite-scroll.ts), keep the story, and repeat step 3 — the list jumps upward as each page settles.

Testing Instructions for Keyboard

Not a UI/markup change. The list can still be scrolled with the keyboard (focus a row, then Page Down / arrow keys); the same stability applies.

Screenshots or screencast

Before After
List jumps upward as each async page settles Scroll position stays put

Use of AI Tools

This change was authored with the assistance of AI tooling (Claude Code). The root cause was diagnosed against a live consumer, the fix and the repro story were reviewed and verified by a human in Storybook before submitting.

The infinite-scroll scroll-anchor restoration could move the scroll
position out from under the user. On a window advance it captures an
anchor element and its offset, then on a later render re-applies that
offset. When pages load asynchronously the user keeps scrolling between
capture and restore, and the restoration treated that user-driven
movement as drift to "correct" — snapping the list back to the
capture-time position and undoing the scroll.

Store the scrollTop at capture and add the intervening scroll delta back
into the adjustment, so only the content-driven shift (items added or
removed above the anchor) is compensated. When the user does not scroll
during the load the delta is zero and behaviour is unchanged.

Also add an "Async Infinite Scroll" story that reveals data one page at
a time asynchronously with a toggling isLoading, which reproduces the
jump; the existing synchronous InfiniteScroll story never exercises the
restoration path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 19, 2026 •

Copy link
Copy Markdown

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] DataViews.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions github-actions Bot added the [Package] DataViews /packages/dataviews label Jun 19, 2026
@github-actions

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: StevenDufresne <dufresnesteven@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

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

Copy link
Copy Markdown
Contributor Author

Superseded by #79335 (re-opened from a branch on the main repo instead of a fork). Same commits.

@StevenDufresne
StevenDufresne deleted the fix/dataviews-infinite-scroll-anchor-jump branch June 19, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] DataViews /packages/dataviews

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant