Skip to content

fix(ios): keep a navigator mounted during hydration (no nav-context error)#111

Merged
os-zhuang merged 1 commit into
mainfrom
fix/ios-startup-navigation-context
Jun 11, 2026
Merged

fix(ios): keep a navigator mounted during hydration (no nav-context error)#111
os-zhuang merged 1 commit into
mainfrom
fix/ios-startup-navigation-context

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Found via iOS Simulator testing

Built and ran the app natively on the iPhone 17 simulator (expo run:ios → 0 errors). On launch, the dev LogBox showed a "Couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?" render error. It never surfaced on web because web hydrates before the splash paints.

Root cause (my own P0 regression)

The original startup-request-storm fix (#103) gated the entire <Stack> behind isReady, rendering a bare <View><ActivityIndicator/></View> splash while hydrating. expo-router requires a navigator on every render — so during the async hydration window (reading the persisted server URL + fetching auth config), a navigation hook fired with no navigator mounted → the error. iOS's slower hydration makes the splash actually paint, exposing it.

Fix

  • Add a real loading route app/index.tsx (no-fetch splash).
  • Mount the <Stack> unconditionally; index is the initial route.
  • useProtectedRoute forwards off index once isReady (added an onIndex case so a signed-in user lands on (tabs)).
  • Push notifications stay gated on isReady.

The request-storm fix is preserved: the data screens still only mount after the guard redirects post-hydration, so they never query the pre-hydration default host. The only early request is a single get-session (harmless, retried at the correct host).

Verification

  • iPhone 17 simulator: 0 navigation-context errors in a fresh Metro session (was thrown on every cold start before).
  • 1298 tests pass, typecheck + lint clean.

Follow-up (separate, not in this PR)

Testing also surfaced a second, distinct native-only error: focusing the Connect screen's Server URL field throws the same "navigation context" error (our Input is a plain TextInput with no nav hooks → a library-level interaction on TextInput focus; web connects fine). It needs isolating (likely react-navigation / react-native-screens) and is tracked separately.

🤖 Generated with Claude Code

…rror)

On native, the root layout's `!isReady` branch rendered a bare splash `View`
with no expo-router navigator. expo-router requires a navigator on every
render, so during the async hydration window (slow enough to actually paint on
iOS, unlike web) navigation hooks threw "Couldn't find a navigation context."

Replace the splash branch with a real loading route (`app/index.tsx`) and mount
the `<Stack>` unconditionally. The initial `index` route is a no-fetch splash
that `useProtectedRoute` forwards off once `isReady`, so the data screens (and
their queries) still never mount against the pre-hydration default host — the
request-storm fix from the original P0 change is preserved. Push notifications
stay gated on `isReady`.

Found via iOS Simulator testing (the error never surfaced on web because web
hydrates before the splash paints). Verified on iPhone 17: the startup
navigation-context error is gone (0 occurrences in a fresh Metro session).
1298 tests pass, typecheck + lint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@os-zhuang os-zhuang merged commit 77ead2a into main Jun 11, 2026
4 checks passed
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