Skip to content

feat(expo): Chris/mobile 405 react native components release#7843

Open
chriscanin wants to merge 66 commits intomainfrom
chris/mobile-405-react-native-components-release
Open

feat(expo): Chris/mobile 405 react native components release#7843
chriscanin wants to merge 66 commits intomainfrom
chris/mobile-405-react-native-components-release

Conversation

@chriscanin
Copy link
Member

@chriscanin chriscanin commented Feb 13, 2026

Description

These changes can be tested by using the snapshot that will be commented in this PR discussion, and installing that into the expo quickstart repo on the branch: chris/mobile-343-bridge-android-to-a-native-module-that-is-available-in-the
(same branch name as here).

https://linear.app/clerk/issue/MOBILE-342/bridge-ios-to-a-native-module-that-is-available-in-the-expo-sdk
MOBILE-289
https://linear.app/clerk/issue/MOBILE-289/expo-google-universal-sign-in

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Native Expo UI: AuthView, InlineAuthView, UserButton, UserProfileView (modal & inline) and native plugin support
    • Native auth flows: Google Sign‑In and Apple Sign‑In; new native session/events hooks and client token access
  • Bug Fixes

    • More reliable native↔JS session sync, improved lifecycle/unmount guards, and clearer error handling
  • Documentation

    • Native iOS setup guide and native components README
  • Chores

    • Expo plugin, packaging, podspec and package version updates

- Implemented UserButton component to open UserProfileView on press.
- Created UserProfile component for comprehensive profile management.
- Integrated native ClerkExpo module for iOS functionality.
- Updated ClerkProvider to configure Clerk iOS SDK.
- Added exports for new components in the native index file.
- Adjusted TypeScript configuration to include additional files.
- Modified build process to temporarily skip declaration generation.
- Updated dependencies in pnpm-lock.yaml for compatibility.
…ge-ios-to-a-native-module-that-is-available-in-the-expo
…ge-android-to-a-native-module-that-is-available-in-the
…ling and UI presentation

- Consolidated Clerk SDK initialization and session management in ClerkExpoModule.
- Removed ClerkProfileActivity and replaced it with ClerkUserProfileActivity for better clarity and functionality.
- Introduced ClerkViewFactory to manage creation of intents for authentication and user profile activities.
- Enhanced error handling and promise management for asynchronous operations.
- Updated SignIn and UserProfile components to synchronize native and JS session states effectively.
- Improved user experience by ensuring the auth modal is always presented, allowing native UI to manage signed-in state.
- Added backward-compatible wrappers for SignedIn and SignedOut components.
- Updated `clerk-android` versions in `build.gradle` to `0.1.30` for API and `0.1.4` for UI.
- Added Kotlin metadata version check skip to address compatibility issues.
- Introduced packaging exclusions for duplicate META-INF files in Android.
- Enhanced `ClerkAuthActivity` to improve session handling and logging.
- Updated `ClerkExpoModule` to include detailed logging for session retrieval.
- Improved `ClerkUserProfileActivity` to handle sign-out detection and logging.
- Refined `SignIn` and `UserProfile` components to prevent duplicate auth callbacks and improve user state management.
- Added packaging exclusions in the Expo config plugin for Android to resolve dependency conflicts.
- Introduced AuthView component to handle sign-in and sign-up using native UI.
- Added AuthView types for better type safety.
- Removed deprecated SignIn component and its types.
- Updated UserButton and UserProfileView components with enhanced documentation.
- Refactored ClerkProvider to sync native sessions with JS SDK.
- Adjusted TypeScript configurations for improved declaration generation.
- packages/expo/package.json: merge new exports (./types) and file entries (google, apple)
- packages/expo/src/hooks/index.ts: use main's standard re-exports for useSignIn/useSignUp/useWaitlist
- packages/expo/src/provider/singleton/createClerkInstance.ts: use @clerk/clerk-js import (not headless subpath)
- packages/react/src/isomorphicClerk.ts: use main's ClerkUI loading pattern via options.ui
@vercel
Copy link

vercel bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Mar 4, 2026 9:54pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 13, 2026

🦋 Changeset detected

Latest commit: 21933a6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/expo Minor

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

@@ -0,0 +1,14 @@
// eslint-disable-next-line simple-import-sort/imports, import/namespace, import/default, import/no-named-as-default, import/no-named-as-default-member
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto, is the order important here to warrant the disables?

if (ClerkExpoModule?.getClientToken) {
const nativeClientToken = await ClerkExpoModule.getClientToken();
if (nativeClientToken) {
await SecureStore.setItemAsync(CLERK_CLIENT_JWT_KEY, nativeClientToken, {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using existing TokenCache if possible, or make it clear why we can't

Comment on lines +8 to +22
/**
* Render children only when the user is signed in.
* A convenience wrapper around `<Show when="signed-in">`.
*/
export function SignedIn({ children }: PropsWithChildren): ReactNode {
return <Show when='signed-in'>{children}</Show>;
}

/**
* Render children only when the user is signed out.
* A convenience wrapper around `<Show when="signed-out">`.
*/
export function SignedOut({ children }: PropsWithChildren): ReactNode {
return <Show when='signed-out'>{children}</Show>;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to diverge from the other SDKs here and continue exporting these?

async signIn(params?: SignInParams): Promise<OneTapResponse> {
try {
return await getNativeModule().signIn(params ?? {});
return (await getNativeModule().signIn((params as any) ?? null)) as unknown as OneTapResponse;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the need for type casting now?

@brkalow
Copy link
Member

brkalow commented Mar 2, 2026

What integration testing tools are available for react native / expo? Would be great to start seeing those get built out.

Maestro seems popular? https://docs.expo.dev/eas/workflows/examples/e2e-tests/

Comment on lines +21 to +32
## 2. Removed legacy subpath exports

The following packages have removed their legacy subpath export mappings:
- `@clerk/expo`
- `@clerk/shared`
- `@clerk/react`
- `@clerk/localizations`

**What changed:**
Previously, these packages used a workaround to support subpath imports (e.g., `@clerk/shared/react`, `@clerk/expo/web`). These legacy exports have been removed in favor of modern package.json `exports` field configuration.

All public APIs remain available through the main package entry points.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be split up into its own changeset so we don't have contaminate changelogs

@jacekradko
Copy link
Member

Code review

Found 5 issues:

  1. Changeset set-minimum-expo-53.md lists packages with no code changes as major bumps. @clerk/shared, @clerk/localizations, and @clerk/expo-passkeys are declared as major but have zero file changes in this PR. The changeset body describes "Removed legacy subpath exports" for these packages, but no such removals exist in the diff. This will cause incorrect major version releases for unchanged packages when changesets runs.

---
'@clerk/expo': major
'@clerk/shared': major
'@clerk/react': major
'@clerk/localizations': major
'@clerk/expo-passkeys': major

  1. Manual version bump in packages/expo/package.json conflicts with changeset automation. The version was manually set to 2.19.24 (from 2.19.10 on main), skipping 14 patch versions. This repo uses changesets to manage versions; manual bumps in package.json will conflict with the automated release pipeline.

"name": "@clerk/expo",
"version": "2.19.24",
"description": "Clerk React Native/Expo library",

  1. app.plugin.js is now 581 lines of standalone JavaScript, diverging from the TypeScript source. Previously this was a one-line shim (module.exports = require('./dist/plugin/withClerkExpo')). It now contains a full standalone implementation of withClerkExpo, withClerkIOS, withClerkAndroid, and withClerkGoogleSignIn while src/plugin/withClerkExpo.ts also exists and is modified. This creates two diverging implementations -- one type-checked and tested, one not.

/**
* Expo config plugin for @clerk/clerk-expo
* Automatically configures iOS and Android to work with Clerk native components
*
* When this plugin is used:
* 1. iOS is configured with Swift Package Manager dependency for clerk-ios
* 2. Android is configured with packaging exclusions for dependencies
*
* Native modules are registered via react-native.config.js and standard
* React Native autolinking (RCTViewManager / ReactPackage).

  1. Generated build artifacts committed to source control. app.plugin.d.ts and app.plugin.d.ts.map are generated TypeScript declaration files that should not be in source control. These are non-standard for this repo where build output goes to dist/.

export = withClerkExpo;
/**
* Combined Clerk Expo plugin
*/
declare function withClerkExpo(config: any): any;
//# sourceMappingURL=app.plugin.d.ts.map

  1. fraudProtection.ts React Native bypass creates an infinite retry loop. When document is undefined, managedChallenge returns { captchaError: 'modal_component_not_ready' }. In execute(), this causes __internal_sendCaptchaToken to be skipped (line 72), but captchaRetryCount is only incremented in the inner catch (line 77) -- which is never hit since managedChallenge resolves successfully. The code then calls return await cb() (line 85) which re-throws requires_captcha, re-enters the outer catch, and the cycle repeats indefinitely. The captchaAttemptsExceeded() guard at line 30 is only checked on fresh execute() calls, not within the loop.

try {
const captchaParams: any = await this.managedChallenge(clerk);
if (captchaParams?.captchaError !== 'modal_component_not_ready') {
await this.client.getOrCreateInstance().__internal_sendCaptchaToken(captchaParams);
this.captchaRetryCount = 0; // Reset the retry count on success
}
} catch (err) {
this.captchaRetryCount++;
throw err;
} finally {
// Resolve the exception placeholder promise so that other exceptions can be handled
resolve();
this.inflightException = null;
}
return await cb();
}
}
public managedChallenge(clerk: Clerk) {
// In React Native environments, DOM-based captcha challenges are not supported.
// Return a no-op result to avoid hanging on DOM APIs that don't exist.
if (typeof document === 'undefined') {
return Promise.resolve({ captchaError: 'modal_component_not_ready' });
}
return new this.CaptchaChallengeImpl(clerk).managedInModal({ action: 'verify' });


Additionally, several issues scored just below the threshold (75/100) that may warrant attention: initStartedRef not reset on unmount blocking StrictMode re-initialization, waitForLoad calling addOnLoaded which only exists on IsomorphicClerk (not the HeadlessBrowserClerk returned by createClerkInstance), static import of optional expo-secure-store, TurboModuleRegistry.getEnforcing crashing at import time for optional Google Sign-In, sessionSyncedRef set before setActive succeeds permanently blocking retries, silent error swallowing in useUserProfileModal/UserButton, and unconditional console.log calls in production code (useNativeAuthEvents.ts).

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

…e module

Move captcha document guard from fraudProtection to CaptchaChallenge.
Add ClerkKeychainService Info.plist support for extension apps.
Use non-enforcing TurboModuleRegistry.get for ClerkGoogleSignIn.
…t-native-components-release

# Conflicts:
#	.changeset/set-minimum-expo-53.md
#	packages/clerk-js/src/utils/captcha/CaptchaChallenge.ts
#	packages/expo/package.json
#	packages/expo/src/components/controlComponents.tsx
The '__clerk_client_jwt' string was duplicated across 4 files. Extract
to a single constant in src/constants.ts.
…ore calls

Replace direct expo-secure-store imports in AuthView, InlineAuthView,
and ClerkProvider with the existing tokenCache from @clerk/expo/token-cache.
This consolidates SecureStore options (AFTER_FIRST_UNLOCK) and error handling
in one place.
Replace plain Error throws with ClerkRuntimeError from @clerk/shared/error,
providing a unique error code (clerk_instance_not_available) and actionable
debugging message.
Prevent unbounded polling in AuthView and InlineAuthView. Caps at 40
polls (60s at 1.5s intervals) before stopping gracefully.
Stop swallowing all errors in presentUserProfile catch blocks. Now only
silences expected dismissal/cancellation errors from the native module
and logs unexpected errors for debugging.
Modal dismissal resolves with { dismissed: true } on the native side,
it does not reject. So the catch block only fires on real errors
(E_NOT_INITIALIZED, E_CREATE_FAILED, E_NO_ROOT_VC). Log them properly
instead of silencing or string-matching.
Log native module errors (E_NOT_INITIALIZED, E_CREATE_FAILED, etc.)
only in development. These are configuration errors actionable during
dev, not runtime errors to surface to end users.
…rename

Move isClerkNetworkError above the createResourceCache block for
visibility. Remove the unnecessary const clerk = __internal_clerk
alias and use __internal_clerk directly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants