Skip to content

fix(scroll): let scrollToIndex reach content after the last item - #2529

Open
cleobnvntra wants to merge 1 commit into
Shopify:mainfrom
cleobnvntra:fix/issue-1923-scroll-to-index-max-offset
Open

cleobnvntra wants to merge 1 commit into
Shopify:mainfrom
cleobnvntra:fix/issue-1923-scroll-to-index-max-offset

Conversation

@cleobnvntra

@cleobnvntra cleobnvntra commented Oct 9, 2026 •

Copy link
Copy Markdown

Description

Fixes #1923

scrollToIndex clamps its final offset with getMaxScrollOffset(), which computes the scrollable length from the header and the items only. Content after the items — ListFooterComponent and contentContainerStyle padding — is not counted, so near the end of the list viewPosition is ignored even though the scroll view can scroll further (you can scroll there manually). This is also why adding paddingBottom doesn't help, while adding a spacer item to data does.

This PR clamps against the content size reported by the scroll view (onContentSizeChange), which includes everything after the items:

  • RecyclerViewManager stores the reported content size and getMaxScrollOffset() uses max(layout size, reported size). Before a size is reported, or if the reported size is stale and smaller (e.g. right after items are appended), it behaves exactly as before.
  • RecyclerView passes its own onContentSizeChange to the scroll view and forwards the call to the user's handler.

Lists without content after the items are unaffected: the reported size equals the layout size.

getMaxScrollOffset() is only used by scrollToIndex, so the change is limited to scrollToIndex, scrollToItem and the scrollToIndex step of scrollToEnd.

Reviewers' hat-rack 🎩

  • getMaxScrollOffset() in RecyclerViewManager.ts: taking the larger of the layout size and the reported size. If data shrinks, the reported size can be larger than the real content until the next onContentSizeChange, so in that window the target can be past the real end. Happy to change this if you'd prefer a different trade-off.
  • onContentSizeChange in RecyclerView.tsx is set after {...rest} and before {...overrideProps}, so a handler passed through overrideProps still takes precedence (and the clamp then falls back to the previous behaviour).

To reproduce: a list with a ListFooterComponent or contentContainerStyle={{ paddingBottom }}, then scrollToIndex({ index: <one of the last items>, viewPosition: 0.5 }). Before: stops short of centering. After: the item is centered. Without footer/padding, the behaviour is unchanged (it stops at the end of the list).

Screenshots or videos

Footer (300px) + paddingBottom (200px), scrollToIndex({ index, viewPosition: 0.5 }). Red line = viewport center.

Before After
Item 22 before-item22 after-item22
Item 24 before-item24 after-item24

Test plan

  • New unit tests in RecyclerView.test.tsx (vertical, horizontal, fallback before a size is reported, stale/smaller size, invalid sizes, user handler forwarding). The two main cases fail on main (Expected: 1500.5, Received: 1101) and pass with the fix.
  • Unit tests pass (yarn test): 206/206
  • Type check passes (yarn type-check)
  • Lint passes on the changed files (yarn lint)
  • Verified on web (fixture/web) with a footer + paddingBottom: last items now center; without them, behaviour unchanged
  • Not verified on an iOS simulator or Android device (no access to a Mac); the change is platform-agnostic TypeScript

scrollToIndex clamped its target offset to a maximum computed from the
header and the items only, ignoring ListFooterComponent and
contentContainerStyle padding. Near the end of the list, viewPosition
was ignored even though the scroll view had room to scroll further.

Clamp against the content size reported by the scroll view instead,
falling back to the layout size until it is reported (or when the
reported size is stale and smaller). onContentSizeChange is still
forwarded to the user's handler.

Fixes Shopify#1923
@cleobnvntra

Copy link
Copy Markdown
Author

I have signed the CLA!

This branch has not been deployed

No deployments
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.

V2: scrollToIndex with viewPosition becomes unreliable when approaching end of list

1 participant