Skip to content

fix: complete non-zero initialScrollIndex on iOS without waiting for native scroll events - #500

Open
erank-pixel wants to merge 2 commits into
LegendApp:mainfrom
erank-pixel:fix/ios-initial-scroll-completion
Open

fix: complete non-zero initialScrollIndex on iOS without waiting for native scroll events#500
erank-pixel wants to merge 2 commits into
LegendApp:mainfrom
erank-pixel:fix/ios-initial-scroll-completion

Conversation

@erank-pixel

Copy link
Copy Markdown

Problem

On iOS, a list mounted with a non-zero initialScrollIndex stays invisible (the readyToRender opacity gate) for seconds — in our production app we consistently measured 2.8–3.8s before the list appeared, while its content was mounted, laid out, and (in our case) already playing video/audio underneath.

Root cause

The initial-scroll completion logic deadlocks on iOS:

  1. The initial scroll is dispatched as an unanimated scrollTo/contentOffset. iOS does not emit a scroll event for unanimated offset changes, so state.hasScrolled never becomes true and didDispatchNativeScroll keeps gating.
  2. shouldFinishInitialScrollWithoutNativeProgress refuses to finish while didDispatchNativeScroll(state) && !state.hasScrolled (and unconditionally for bootstrap sessions) — even when state.scroll and state.scrollPending already match the target exactly.
  3. The fallback loop in checkFinishedScroll then re-issues scrollToFallbackOffset to the same offset — which again emits no event, because the offset doesn't change.
  4. The loop burns through its retries until the watchdog gives up (~2–4s depending on timing), and only then does readyToRender flip and the list appear.

Which of the two refusals bites depends on mount timing (session kind ends up bootstrap on some mounts and offset on others), which makes the stall look intermittent — that's what made it painful to track down.

Fix

On iOS only, allow shouldFinishInitialScrollWithoutNativeProgress to finish without observed native progress — the existing offset checks right below (state.scroll and state.scrollPending within 1px of the target) remain the real gate, and they're reliable there because unanimated scrollTo applies synchronously. Android/web still emit the events and keep the stricter path, so their behavior is unchanged.

After the fix our reveal times went from 2.8–3.8s to a consistent 120–370ms across dozens of cold mounts (production app, iPhone 13 / iOS 26, ~60-item list with fixed item heights, initialScrollIndex typically 1–10).

Repro

  • iOS device or simulator
  • <LegendList data={items} initialScrollIndex={8} estimatedItemSize={FIXED} …/> where the list mounts with data already present
  • Observe onLoad timing / the list staying blank for seconds before appearing (worse on slower devices; timing-dependent, so try several mounts)

Notes

  • I scoped both bypasses to Platform.OS === "ios" to keep the blast radius minimal, but if you'd prefer the offset-match check to be authoritative on all platforms (or a different structure entirely), happy to rework.
  • Wasn't able to run bun test in this environment; the change has been running in a production app for a full day of heavy manual testing (cold mounts, remounts, in-place data swaps via dataKey, user scrolls immediately after mount) with no regressions observed.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08c84df915

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/core/checkFinishedScroll.ts Outdated
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.

1 participant