You have flipped the switch. You enabled newArchEnabled in your gradle.properties and Podfile, perhaps even set bridgelessEnabled to true in your Expo config. The build succeeds, but the moment the app launches, it crashes with a red screen (or silent native crash) pointing to:
Uncaught Error: TurboModuleRegistry.getEnforcing(...): 'X' could not be found.
This is the most common blockade when migrating to React Native 0.74+ and the New Architecture. It indicates a severance between your JavaScript Interface (JSI) expectations and the underlying native bindings.
The Root Cause: Synchronous JSI vs. Asynchronous Bridge
To fix this, you must understand the architectural shift.
In the Old Architecture, NativeModules.MyModule was a proxy object. If the native module failed to load, the proxy might just be empty, or calls would fail silently across the asynchronous bridge.
In the New Architecture (Bridgeless), we use TurboModules. These are loaded via JSI (JavaScript Interface). When your JavaScript code calls TurboModuleRegistry.getEnforcing('MyModule'), it is asking the C++ layer for a synchronous, direct reference to a host object.
The error occurs because:
- Codegen Mismatch: The JavaScript spec expects a TurboModule, but the native side hasn't generated or registered the C++ header files.
- Interop Failure: The module is legacy (Java/Obj-C only), and the React Native Interop Layer—which wraps legacy modules in a TurboModule shell—failed to auto-detect it.
- Registry Lookup:
getEnforcingis strict. Unlikeget(which returns nullable),getEnforcingthrows immediately if the module isn't in the map.
The Fix: Explicit Interop and Codegen Specification
We will solve this in two steps. First, we force the Interop Layer to recognize third-party legacy libraries. Second, we migrate a custom module to strictly satisfy the getEnforcing contract using TypeScript specifications.
Solution 1: Forcing Legacy Libraries into the Interop Layer
If the crash stems from a third-party library (e.g., an analytics or storage SDK) that hasn't officially migrated, you must explicitly register it in the Interop Layer via react-native.config.js.
Create or update react-native.config.js at your project root:
module.exports = {
project: {
ios: {
automaticPodsInstallation: true,
},
android: {},
},
// Add this dependencies section
dependencies: {
'legacy-library-causing-crash': {
platforms: {
android: {
// Force the library to be treated as a legacy module wrapped by the Interop layer
componentDescriptors: null,
cmakeListsPath: null,
},
ios: {},
},
},
},
};
Note: In rare cases where autolinking fails entirely for Bridgeless, you may need to patch the library's podspec or build.gradle to ensure it is included in the build graph before the Interop layer can see it.
Solution 2: Migrating Custom Modules (The Permanent Fix)
If the crashing module is your own, you must implement the TurboModule Spec. This ensures TurboModuleRegistry.getEnforcing finds the generated C++ bindings.
Step 1: Define the TypeScript Specification
Create a file named NativeBiometricAuth.ts. The naming convention Native<Name>.ts is critical for Codegen discovery.
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
// Define strict types for your methods
authenticate(reason: string): Promise<boolean>;
checkAvailability(): boolean;
// Events are handled differently in TurboModules,
// but for strict invocation, we focus on methods.
}
// 1. We attempt to get the module strictly.
// 2. If this fails, the app crashes, alerting us to a native binding issue immediately.
export default TurboModuleRegistry.getEnforcing<Spec>('BiometricAuth');
Step 2: Configure Codegen
In your package.json, configure the Codegen to parse this file.
{
"name": "my-app",
"dependencies": {
"react-native": "0.76.1"
},
"codegenConfig": {
"name": "AppSpecs",
"type": "modules",
"jsSrcsDir": "src/specs",
"android": {
"javaPackageName": "com.myapp.specs"
}
}
}
Run npx expo prebuild --clean (or pod install) to trigger the generation of C++ scaffolding.
Step 3: Connect Native Implementation (iOS Example)
The error usually persists because the native class doesn't conform to the generated protocol. You must modify your Objective-C++ header.
File: ios/BiometricAuth.h
#import <Foundation/Foundation.h>
#import <AppSpecs/AppSpecs.h> // Import the generated header
// The class must conform to the generated spec protocol
@interface BiometricAuth : NSObject <NativeBiometricAuthSpec>
@end
File: ios/BiometricAuth.mm
#import "BiometricAuth.h"
@implementation BiometricAuth
RCT_EXPORT_MODULE()
// The method signature must match the TypeScript Spec exactly
- (void)authenticate:(NSString *)reason
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
// Logic here...
BOOL success = YES;
resolve(@(success));
}
// Sync method implementation
- (NSNumber *)checkAvailability {
return @(YES);
}
// Required for TurboModules to work with the compiled spec
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeBiometricAuthSpecJSI>(params);
}
@end
Why This Works
The crash occurs because getEnforcing performs a lookup in the TurboModule C++ registry.
- By defining
Native<Name>.ts: You provide the source of truth for Codegen. - By running Prebuild/Pod Install: React Native generates a C++ header (
AppSpecs.h) that defines the JSI interface. - By implementing
getTurboModulein Objective-C++: You physically link your native class instance to that JSI interface.
When JavaScript executes TurboModuleRegistry.getEnforcing('BiometricAuth'), the runtime now finds the registered JSI binding created in the getTurboModule method, preventing the crash.
Conclusion
The transition to Bridgeless mode removes the runtime safety net of the asynchronous bridge. TurboModuleRegistry.getEnforcing is designed to fail hard and fast when native bindings are missing.
To solve these crashes, stop treating native modules as "magic strings" looked up at runtime. Either explicitly register legacy modules in the Interop layer via config or, ideally, migrate your custom modules to strict TurboModule specifications to leverage the full performance benefits of the JSI.