Skip to content

Anchor VirtualizedList interior spacers to the cell before them - #58916

Open
mozzius wants to merge 1 commit into
react:mainfrom
mozzius:virtualized-list-interior-spacer
Open

mozzius wants to merge 1 commit into
react:mainfrom
mozzius:virtualized-list-interior-spacer

Conversation

@mozzius

@mozzius mozzius commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Summary:

After a large prepend under maintainVisibleContentPosition, the spacer between the cells kept by initialNumToRender and the render window starts at averageCellLength * first rather than where the kept cells end. The cells after it then move each time a cell mounts or unmounts at the window's leading edge, and mVCP's correction moves that edge back, so the content height and offset swing by ~400pt about 15 times a second, indefinitely. This starts a spacer whose first cell is unmeasured where the rendered cell before it ends; only the start is anchored, because sizing it from the cell after it as well would include any gap/rowGap or CellRendererComponent margins and grow by twice that on every render.

Fixes #58870. Repro: https://github.com/mozzius/virtualizedlist-spacer-ring-repro

Changelog:

[GENERAL] [FIXED] - VirtualizedList: anchor an interior spacer's start to the rendered cell before it, so maintainVisibleContentPosition doesn't oscillate after a large prepend

Test Plan:

  • New test in VirtualizedList-test.js, with and without a 10pt gap between cells: fails on main (spacer 1166.7 / 1176.7, expected 1000) and passes with the fix.

  • yarn jest packages/virtualized-lists: 187 passed, all 69 snapshots unchanged. yarn flow-check, eslint and prettier are clean.

  • The repro on an iPhone 17 Pro simulator (iOS 26.5, RN 0.87.1), 4 identical runs per row. Offset jumps the list made by itself after the prepend / while scrolling up in 50pt steps / in 5s at rest:

    Stock Fix
    No gap 3 / 6 / 75 (±449pt) 2 / 1 / 0
    gap: 10 3 / 7 / 75 (±398pt) 2 / 1 / 0

    Scrolling 3000pt up through the prepended rows: stock 121 jumps with 109 reversals, the fix 2 with none, with or without the gap. Sizing the spacer from both neighbours instead made 21 jumps of exactly +20pt (twice the gap).

🤖 Generated with Claude Code

An interior spacer whose first cell is unmeasured started at
averageCellLength * index instead of where the rendered cell before it
ends. After a large prepend under maintainVisibleContentPosition, the
cells after it then moved each time a cell mounted or unmounted at the
render window's leading edge, and the list oscillated indefinitely.

Fixes react#58870.

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

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

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VirtualizedList: interior spacer mixes measured and estimated offsets, so maintainVisibleContentPosition oscillates endlessly after a large prepend

1 participant