Skip to content

fix(mobile): don't leave the splash up when the preload beats the first effect - #14658

Merged
dylanjeffers merged 1 commit into
mainfrom
fix/mobile-preload-gate-race
Oct 9, 2026
Merged

dylanjeffers merged 1 commit into
mainfrom
fix/mobile-preload-gate-race

Conversation

@dylanjeffers

Copy link
Copy Markdown
Contributor

Summary

Cold launches can stay on the splash screen forever. App renders null until the AsyncStorage preload finishes, and its useEffectOnce only subscribed to localStoragePreloadPromise when the module-level localStoragePreloaded flag was still false. If the preload resolves after the first render but before the effect runs, the flag is already true, the effect skips the subscription, and nothing ever sets preloaded. App keeps returning null, RootScreen never mounts, and BootSplash.hide is never called.

The fix subscribes unconditionally. setPreloaded(true) is idempotent, so the extra .then on an already-resolved promise costs nothing.

Why now

Found while smoke-testing the New Architecture flip (#14657). On the new architecture, passive effects run later relative to the first render, which opens the window. On the iOS 26.5 simulator with the 1.5.189 new-arch Release build:

  • without this fix, 5 of 6 cold launches of the as-built bundle stuck on the splash (JS idle, RCTRootComponentView with no children). Instrumented runs logged App render preloaded=false, then preload resolved, and no further render.
  • with this fix, 8 of 8 cold launches reached the feed.

The same race exists on the old architecture (introduced in #14259). It just loses it less often. 3 of 3 old-arch 1.5.188 launches were fine.

Rollout

JS-only. It's safe to OTA to 1.5.188. It has to be on main before #14656 / #14657 merge, so the 1.5.189 binary ships with it. Rebase or merge main into chore/mobile-native-release-1.5.189 after this lands.

Checks

  • eslint packages/mobile/src/app/App.tsx: pass
  • npx tsc --noEmit -p packages/mobile: pass

🤖 Generated with Claude Code

…st effect

App's useEffectOnce skipped subscribing to localStoragePreloadPromise when
the module-level flag was already true. If the preload resolved after the
first render (preloaded=false) but before the effect ran, nothing ever set
the state, so App rendered null and the splash stayed up. Subscribing
unconditionally is safe: setPreloaded(true) is idempotent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: b206771

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dylanjeffers
dylanjeffers marked this pull request as ready for review October 9, 2026 23:49
@dylanjeffers
dylanjeffers merged commit 96b727b into main Oct 9, 2026
3 checks passed
@dylanjeffers
dylanjeffers deleted the fix/mobile-preload-gate-race branch October 9, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant