feat(expo): let native components keep their own chrome when embedded - #9121
Conversation
🦋 Changeset detectedLatest commit: 702952c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds optional Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
7e14106 to
b14050e
Compare
f096102 to
1ec4d8d
Compare
API Changes Report
Summary
@clerk/expoCurrent version: 4.1.2 Subpath
|
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt (1)
63-126: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winAlign Android
embeddedNavigationwith the iOS lifecycle in both native views. Android createsembeddedNavigationunconditionally, passes it toAuthView/UserProfileViewonly whenhideHeaderis true, and still calls itspop()/popToRoot()fromgoBack()/popToRoot(). Like iOS, make the field optional, create it in the composable/host view lifecycle only whenhideHeaderis true, and callpop()/popToRoot()through?..
packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.ktpackages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt` around lines 63 - 126, The Android native views currently instantiate embedded navigation unconditionally; align both lifecycle implementations with iOS. In packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt lines 63-126, make embeddedNavigation nullable, initialize it only when hideHeader is true within the composable/host lifecycle, and invoke pop() and popToRoot() from goBack() and popToRoot() via safe calls. Apply the same changes to packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt lines 32-76, preserving the existing AuthView and UserProfileView navigation behavior.
🧹 Nitpick comments (2)
packages/expo/src/native/__tests__/router.test.tsx (1)
36-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest coverage gap: only
AuthScreendismiss path is exercised.
UserProfileScreen(new public export) has no dedicated test, and neither screen has a test for the hardware-back-press →goBack()path or thescreenOptionstoggling (headerBackVisible/gestureEnabled/headerLeft) based oncanGoBack, which is the core behavior this file adds. As per coding guidelines, "Unit tests are required for all new functionality" and "Include tests for all new features" for test files.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/expo/src/native/__tests__/router.test.tsx` around lines 36 - 72, The test suite currently covers only AuthScreen’s dismiss action. Extend the AuthScreen tests and add dedicated UserProfileScreen tests to cover hardware-back-press invoking goBack(), plus screenOptions behavior for both canGoBack states, including headerBackVisible, gestureEnabled, and headerLeft; retain the existing dismiss assertions.Source: Coding guidelines
packages/expo/src/native/router.tsx (1)
76-76: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
useRef(fn()).currentre-invokes the loader on every render.
useRef(loadExpoRouter())(lines 145, 202) anduseRef(loadHeaderBackButton())(line 76) pass a freshly computed value as theuseRefargument; JS evaluates that argument on every render even though only the first value is retained. This meansrequire()/error-throwing runs on every render, not just mount.♻️ Lazy-init pattern
-export function UserProfileScreen({ onDismiss, style, options }: UserProfileScreenProps): ReactElement { - const router = useRef(loadExpoRouter()).current; +export function UserProfileScreen({ onDismiss, style, options }: UserProfileScreenProps): ReactElement { + const routerRef = useRef<ExpoRouterModule | null>(null); + if (routerRef.current === null) { + routerRef.current = loadExpoRouter(); + } + const router = routerRef.current;Apply the same pattern to
HeaderBackButtoninuseEmbeddedScreenand toAuthScreen.Also applies to: 145-145, 202-202
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/expo/src/native/router.tsx` at line 76, Replace eager loader evaluation in useEmbeddedScreen’s HeaderBackButton initialization and the corresponding AuthScreen loadExpoRouter initializations with lazy ref initialization: create the ref without invoking the loader, then call the loader only when the ref’s current value is unset. Apply this to HeaderBackButton and both loadExpoRouter usages, preserving the existing loaded values and behavior after initialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/hosted-navigation-native-views.md:
- Line 12: Update the release note wording from “hosted-navigation support” to
“embedded-navigation support” when describing the required clerk-ios and
clerk-android SDK releases, preserving the rest of the note unchanged.
In
`@packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt`:
- Around line 63-64: Make embeddedNavigation nullable and only initialize it
when hideHeader is true, matching the AuthView usage in Content(). Update the
goBack()/popToRoot() callbacks and any AuthView argument handling to safely
operate on the nullable navigation, avoiding mutations when no AuthView is using
it.
In
`@packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt`:
- Around line 32-35: Align goBack() and popToRoot() with the embeddedNavigation
instance passed by Content() to UserProfileView(...): invoke navigation only
when hideHeader is enabled and the rendered instance is available, otherwise
no-op. Remove the unconditional dereferences of the independently created
embeddedNavigation so these controls remain connected to the rendered
navigation.
In `@packages/expo/package.json`:
- Line 156: Update the scoped Clerk dependency entries in
packages/expo/package.json, including expo-google-signin and expo-passkeys, to
use their published `@clerk/` package names while preserving the existing version
ranges.
In `@packages/expo/src/native/router.tsx`:
- Around line 52-56: The loadHeaderBackButton function must stop throwing
synchronously during render: resolve HeaderBackButton through
expo-router/react-navigation first, fall back to `@react-navigation/elements` for
older setups, and wrap both attempts in the same guarded try/catch pattern used
by loadExpoRouter. Propagate failure through useEmbeddedScreen rather than
introducing repeated render-time exceptions, while preserving the existing
HeaderBackButton return contract.
---
Outside diff comments:
In
`@packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt`:
- Around line 63-126: The Android native views currently instantiate embedded
navigation unconditionally; align both lifecycle implementations with iOS. In
packages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.kt
lines 63-126, make embeddedNavigation nullable, initialize it only when
hideHeader is true within the composable/host lifecycle, and invoke pop() and
popToRoot() from goBack() and popToRoot() via safe calls. Apply the same changes
to
packages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.kt
lines 32-76, preserving the existing AuthView and UserProfileView navigation
behavior.
---
Nitpick comments:
In `@packages/expo/src/native/__tests__/router.test.tsx`:
- Around line 36-72: The test suite currently covers only AuthScreen’s dismiss
action. Extend the AuthScreen tests and add dedicated UserProfileScreen tests to
cover hardware-back-press invoking goBack(), plus screenOptions behavior for
both canGoBack states, including headerBackVisible, gestureEnabled, and
headerLeft; retain the existing dismiss assertions.
In `@packages/expo/src/native/router.tsx`:
- Line 76: Replace eager loader evaluation in useEmbeddedScreen’s
HeaderBackButton initialization and the corresponding AuthScreen loadExpoRouter
initializations with lazy ref initialization: create the ref without invoking
the loader, then call the loader only when the ref’s current value is unset.
Apply this to HeaderBackButton and both loadExpoRouter usages, preserving the
existing loaded values and behavior after initialization.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: aef48b31-b970-4d9a-9ea3-3ccf7ae34b66
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (20)
.changeset/hosted-navigation-native-views.mdpackages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.ktpackages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.ktpackages/expo/ios/ClerkAuthNativeView.swiftpackages/expo/ios/ClerkNativeBridge.swiftpackages/expo/ios/ClerkUserProfileNativeView.swiftpackages/expo/native/router/package.jsonpackages/expo/package.jsonpackages/expo/src/native/AuthView.tsxpackages/expo/src/native/AuthView.types.tspackages/expo/src/native/EmbeddedNavigation.types.tspackages/expo/src/native/UserProfileView.tsxpackages/expo/src/native/__tests__/UserProfileView.test.tsxpackages/expo/src/native/__tests__/router.test.tsxpackages/expo/src/native/index.tspackages/expo/src/native/router.tsxpackages/expo/src/specs/NativeClerkAuthView.android.tspackages/expo/src/specs/NativeClerkAuthView.tspackages/expo/src/specs/NativeClerkUserProfileView.android.tspackages/expo/src/specs/NativeClerkUserProfileView.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
Adds hideHeader, onNavigationChange, and goBack()/popToRoot() refs to the native UserProfileView and AuthView so they can be pushed onto a host navigation stack without a double header, plus a new @clerk/expo/native/router entry with prewired expo-router screens (UserProfileScreen, AuthScreen). expo-router becomes an optional peer dependency. Native halves require clerk-ios and clerk-android releases with hosted navigation support before the pods/gradle modules compile.
Embedding previously hid Clerk's navigation chrome and asked the host to rebuild it: the host drew the header, read the component's internal depth, and drove pops back across the bridge. Back never animated that way, because a navigation change arriving from outside the component's own event handling applies without a transition. The components now keep their chrome when pushed onto a host stack, so screen titles, back buttons, swipe-back, and transitions are the platform's own. The host hides its route header and supplies only the root back button through hostBackButton / onHostBack. hideHeader, onNavigationChange, and the goBack / popToRoot refs are gone along with the machinery behind them.
The prewired expo-router screens bundled three lines of boilerplate with one opinionated behavior: pop exactly one route when the flow ends. That policy doesn't fit apps that swap content in place on the same route, and it cost a separate entry point, an optional expo-router peer dependency, a lazy require, and a Metro stub directory to deliver. Apps now hide their own header and pass onHostBack, which both enables the back button and receives the tap, so the redundant hostBackButton flag is gone from the public API. Flow completion is the app's call: react to auth state to swap content or pop the route.
The export pointed at a file that no longer ships, and expo-router was still declared as an optional peer for it.
It described the removed embedded-navigation handle, and its trailing @mainactor attached itself to the next declaration.
It still resolved expo-router after the optional peer dependency was dropped.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
integration/tests/expo-native/flows/embedded-profile-host-back.yaml (1)
1-35: 🩺 Stability & Availability | 🔵 TrivialVerify native-platform execution before release.
The flow exercises host-back behavior through native navigation. Android and iOS use separate native implementations. Confirm that this flow runs on both platforms, especially Android, before treating it as release coverage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@integration/tests/expo-native/flows/embedded-profile-host-back.yaml` around lines 1 - 35, Run the embedded UserProfileView host-back flow on both Android and iOS native implementations, with particular attention to Android, and verify that all navigation and sign-out assertions pass before considering this release coverage.packages/expo/src/native/AuthView.tsx (1)
43-50: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore explicit return types for exported components.
AuthViewremoves its explicit return type.UserProfileViewalso relies on inference. Declare an explicit React element return type for both public APIs.
packages/expo/src/native/AuthView.tsx#L43-L50: Add an explicit return type toAuthView.packages/expo/src/native/UserProfileView.tsx#L67-L67: Add an explicit return type toUserProfileView.As per coding guidelines, “Always define explicit return types for functions, especially public APIs”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/expo/src/native/AuthView.tsx` around lines 43 - 50, Restore explicit React element return types on the public AuthView and UserProfileView component functions. Update packages/expo/src/native/AuthView.tsx lines 43-50 and packages/expo/src/native/UserProfileView.tsx line 67; no other behavior changes are needed.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.changeset/hosted-navigation-native-views.md:
- Around line 5-16: Update the changeset documentation to explicitly list the
removed router entry points, custom header controls, navigation callbacks, and
imperative navigation refs. Clarify that integrations using those APIs must
render UserProfileView or AuthView in the host route and use the optional
onHostBack callback to dismiss it, while preserving the unchanged-usage note
only for integrations that do not depend on the removed APIs.
In `@packages/expo/src/native/__tests__/UserProfileView.test.tsx`:
- Around line 51-70: Add AuthView tests mirroring the UserProfileView host-back
cases: verify a provided onHostBack enables hostBackButton and invokes the
callback, and verify omitting onHostBack disables hostBackButton and leaves the
native onHostBack prop undefined.
---
Nitpick comments:
In `@integration/tests/expo-native/flows/embedded-profile-host-back.yaml`:
- Around line 1-35: Run the embedded UserProfileView host-back flow on both
Android and iOS native implementations, with particular attention to Android,
and verify that all navigation and sign-out assertions pass before considering
this release coverage.
In `@packages/expo/src/native/AuthView.tsx`:
- Around line 43-50: Restore explicit React element return types on the public
AuthView and UserProfileView component functions. Update
packages/expo/src/native/AuthView.tsx lines 43-50 and
packages/expo/src/native/UserProfileView.tsx line 67; no other behavior changes
are needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 0a03a0e7-b9ed-4add-9dc2-c07bdb739761
📒 Files selected for processing (17)
.changeset/hosted-navigation-native-views.mdintegration/templates/expo-native/App.tsxintegration/tests/expo-native/flows/embedded-profile-host-back.yamlpackages/expo/android/src/main/java/expo/modules/clerk/ClerkAuthViewModule.ktpackages/expo/android/src/main/java/expo/modules/clerk/ClerkUserProfileViewModule.ktpackages/expo/ios/ClerkAuthNativeView.swiftpackages/expo/ios/ClerkNativeBridge.swiftpackages/expo/ios/ClerkUserProfileNativeView.swiftpackages/expo/src/native/AuthView.tsxpackages/expo/src/native/EmbeddedNavigation.types.tspackages/expo/src/native/UserProfileView.tsxpackages/expo/src/native/__tests__/UserProfileView.test.tsxpackages/expo/src/native/index.tspackages/expo/src/specs/NativeClerkAuthView.android.tspackages/expo/src/specs/NativeClerkAuthView.tspackages/expo/src/specs/NativeClerkUserProfileView.android.tspackages/expo/src/specs/NativeClerkUserProfileView.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
clerk/clerk_go(manual)clerk/dashboard(manual)clerk/accounts(manual)clerk/backoffice(manual)clerk/clerk(manual)clerk/clerk-docs(manual)clerk/cloudflare-workers(manual)
|
Follow-up on the two review-summary nitpicks:
I also refreshed the PR body to reflect the merged native PRs, released/pinned SDK versions, and completed platform verification. Written by Codex |
Description
UserProfileViewandAuthViewrender their own navigation header, so pushing them onto an expo-router stack produces a double header, and the route's back button can't reach Clerk's internal screens. Requested by a customer in this thread.Hide your route's header and pass
onHostBack. That's the whole API:The component keeps its own chrome, so screen titles, back buttons, swipe-back, and transitions inside it are the platform's own — Clerk's navigation stays driven by Clerk's own buttons, which is what keeps the transitions native.
onHostBackboth shows a back button on the component's root screen, where it has nothing of its own to go back to, and tells you when it's tapped.The component never leaves the route on its own. React to auth state for flow completion, either swapping content in place on one route:
or popping the route when a session becomes active. Both are in the demo app.
Native halves
clerk-ios#519 and clerk-android#798 are merged and released in clerk-ios 1.3.6 and clerk-android 1.0.39.
@clerk/exponow pins those versions.Verified end to end on both iOS and Android with the NativeComponentQuickstart demo: pushed route, internal push and pop, host back at the root, sign-in, sign-out, and a settings form sheet. The Expo native CI fixture also passes its embedded profile host-back round trip on both platforms.
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
🤖 Generated with Claude Code