How React Native Implements Its JavaScript to Native Bridge

React Native's JavaScript-to-Native bridge operates through a three-layer architecture comprising a platform-specific Bridge object, a JavaScript executor running Hermes or JSC, and a C++ ModuleRegistry that marshals bidirectional calls via the facebook::react::Instance class.

The JavaScript-to-Native bridge serves as the core communication layer in React Native, enabling JavaScript code to invoke platform-specific methods written in Objective-C, Swift, Java, or Kotlin. According to the facebook/react-native source code, this bridge relies on a shared C++ runtime wrapped by platform-specific APIs, allowing seamless interoperability while maintaining type safety and performance across iOS and Android platforms.

The Three-Layer Bridge Architecture

The bridge implementation divides responsibilities across three distinct layers that work in concert to mediate between JavaScript and native code.

The Bridge object provides the public API that applications interact with directly. On iOS, this is RCTBridge (defined in React/Base/RCTBridge.h), while Android uses CatalystInstanceImpl (located in ReactAndroid/src/main/jni/react/jni/CatalystInstanceImpl.cpp). This layer creates the JavaScript thread, instantiates the C++ runtime, and manages the lifecycle of native modules.

The JavaScript executor runs the application bundle using engines like Hermes or JavaScriptCore. The executor exposes callJSFunction and callJSCallback interfaces that allow the bridge to forward native-to-JavaScript calls. Key implementations include JSIExecutor.cpp (in ReactCommon/jsiexecutor/jsireact/) and HermesExecutorFactory.h (in ReactCommon/hermes/executor/).

The Module registry and native-module proxy layer converts calls between the two worlds. The C++ Instance class owns a ModuleRegistry (implemented in ReactCommon/cxxreact/ModuleRegistry.cpp) containing C++ wrappers (JSINativeModules) around each native module. When JavaScript calls NativeModules.MyModule.myMethod(), this layer creates call objects containing [moduleId, methodId, args, callId] and routes them through the appropriate platform channels.

Core Bridge Components

The Bridge Object

The bridge object serves as the primary entry point for both platforms. On iOS, the concrete implementation RCTCxxBridge (found in React/CxxBridge/RCTCxxBridge.mm) handles the bridge lifecycle. During its start method, it spins up the JavaScript thread, instantiates the C++ facebook::react::Instance stored as _reactInstance, and builds a JSExecutorFactory (Hermes by default) passed to Instance::initializeBridge.

On Android, CatalystInstanceImpl manages the same responsibilities through JNI, holding a std::unique_ptr<Instance> and registering native modules via JavaModuleWrapper classes.

The JavaScript Executor

The executor layer initializes the JavaScript runtime and processes the application bundle. When RCTCxxBridge starts without a specified executor class, it defaults to HermesExecutorFactory, which creates a JSIExecutor instance. This executor installs JavaScript interface (JSI) bindings that allow direct memory sharing between C++ and JavaScript, eliminating serialization overhead for certain operations.

The executor processes calls through JSIExecutor::callFunction, which the C++ Instance invokes when native code needs to trigger JavaScript functions.

The Module Registry

The ModuleRegistry maintains all registered native modules in a centralized C++ registry. When JavaScript accesses NativeModules, the JSINativeModules class (implemented in ReactCommon/jsiexecutor/jsireact/JSINativeModules.cpp) resolves module and method IDs.

On iOS, RCTModuleData (defined in React/Base/RCTModuleData.h) wraps concrete module classes conforming to the RCTBridgeModule protocol, lazily creating instances and recording method maps. The C++ side mirrors this structure, ensuring that calls from Instance::callJSFunction resolve to the correct native implementation.

JavaScript-to-Native Call Flow

The path from JavaScript to native code follows a precise sequence through the bridge layers.

  1. JavaScript generates the call. When code executes NativeModules.MyModule.doSomething('hello'), the batched bridge in Libraries/BatchedBridge/NativeModules.js creates a call object containing the module ID, method ID, arguments, and call ID. This object pushes onto a message queue monitored by the bridge.

  2. The bridge receives and forwards the call. On iOS, RCTCxxBridge receives the call via enqueueJSCall: and forwards it to the C++ instance:

    [self _runAfterLoad:^{
      strongSelf->_reactInstance->callJSFunction(
         [module UTF8String], [method UTF8String],
         convertIdToFollyDynamic(args ?: @[]));
    }];
  3. The C++ runtime resolves the module. Instance::callJSFunction looks up the target module in its ModuleRegistry using JSINativeModules, finds the matching method via the recorded method map, and invokes the Objective-C implementation through RCTModuleData.instance or the Android JavaModuleWrapper equivalent.

Native-to-JavaScript Call Flow

Native modules communicate back to JavaScript using the bridge's callback APIs. Native code calls enqueueJSCall: on iOS:

[self.bridge enqueueJSCall:@"RCTDeviceEventEmitter"
                    method:@"emit"
                      args:@[ @"MyEvent", payload ]
                completion:nil];

This call queues on the JavaScript thread. When the JSIExecutor processes the queue, it executes the corresponding JavaScript function (such as RCTDeviceEventEmitter.emit), delivering the native event to JavaScript listeners. On Android, the JNI method jniCallJSCallback performs the equivalent function, forwarding native calls to Instance::callJSCallback.

Platform-Specific Implementations

iOS Bridge Architecture

iOS implements the bridge through Objective-C++ wrappers around the C++ core. RCTBridge.h defines the public API, while RCTCxxBridge.mm contains the concrete implementation managing the JavaScript thread and C++ Instance. Native modules implement the RCTBridgeModule protocol from React/Base/RCTBridgeModule.h, with the bridge building a registry of RCTModuleData objects that handle lazy instantiation and method resolution.

Android Bridge Architecture

Android bridges Java and C++ through JNI using CatalystInstanceImpl.cpp. This class registers native modules wrapped in JavaModuleWrapper objects and forwards Java-side calls to the C++ Instance::callJSFunction via jniCallJSFunction. The architecture mirrors iOS but adapts to Android's JNI requirements, maintaining the same three-layer separation between Java, C++, and JavaScript execution contexts.

TurboModules and Bridgeless Mode

When the TurboModule feature flag is enabled, React Native enters a "bridgeless" mode that bypasses the legacy RCTBatchedBridge message queue. In this configuration, native modules register directly with the C++ ModuleRegistry through the TurboModuleRegistry, eliminating the overhead of batched call serialization.

The same facebook::react::Instance class continues to mediate calls, but lookup occurs directly via C++ module proxies rather than through RCTModuleData or JavaModuleWrapper. The public JavaScript API remains unchanged, ensuring backward compatibility while improving performance through synchronous native method calls where appropriate.

Summary

  • The bridge architecture consists of three layers: the platform-specific Bridge object (RCTBridge/CatalystInstanceImpl), the JavaScript executor (JSIExecutor with Hermes/JSC), and the C++ ModuleRegistry with JSINativeModules proxies.
  • Bidirectional communication flows through the C++ facebook::react::Instance class using callJSFunction for native-to-JS calls and callJSCallback for JS-to-native callbacks.
  • iOS implements the bridge via RCTCxxBridge.mm and RCTModuleData, while Android uses CatalystInstanceImpl.cpp with JNI and JavaModuleWrapper.
  • TurboModules provide a bridgeless optimization path that registers C++ modules directly with the ModuleRegistry, bypassing legacy message batching while maintaining the same core mediation layer.

Frequently Asked Questions

How does the React Native bridge handle asynchronous operations between JavaScript and native code?

The bridge operates on a dedicated JavaScript thread separate from the main UI thread. Native modules can expose asynchronous methods using promises or callbacks, with the bridge queueing calls via enqueueJSCall: on iOS or jniCallJSFunction on Android. The C++ Instance class ensures thread-safe marshaling of arguments and return values between the JS executor and native module implementations.

What is the difference between the legacy bridge and TurboModules?

The legacy bridge uses a batched message queue where calls serialize into [moduleId, methodId, args] arrays processed by Libraries/BatchedBridge/NativeModules.js. TurboModules eliminate this batching overhead by registering C++ modules directly with the ModuleRegistry, enabling synchronous calls and reducing marshaling latency. Both architectures use the same facebook::react::Instance class for core mediation, but TurboModules bypass the Objective-C RCTModuleData and Java JavaModuleWrapper proxies when possible.

Can developers use JavaScript engines other than Hermes with the React Native bridge?

Yes, the bridge supports multiple JavaScript engines through the JSExecutorFactory interface. While Hermes is the default (configured in HermesExecutorFactory.h), the bridge can instantiate JavaScriptCore or V8 engines by providing alternative factory implementations to RCTCxxBridge on iOS or CatalystInstanceImpl on Android. The JSIExecutor (in JSIExecutor.cpp) abstracts engine-specific details, providing a uniform interface for the C++ Instance to execute calls.

How are native module methods exposed to JavaScript in the bridge?

Native modules implement platform-specific protocols (RCTBridgeModule on iOS or ReactContextBaseJavaModule on Android) and use macros like RCT_EXPORT_METHOD or @ReactMethod to annotate methods. The bridge scans these annotations during initialization, building method maps in RCTModuleData (iOS) or JavaModuleWrapper (Android). The JSINativeModules C++ class then exposes these methods to JavaScript through the JSI layer, allowing direct invocation via the NativeModules JavaScript API.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →