When JavaScript Isn't Enough: The Case for Native Modules
React Native provides a vast set of cross-platform APIs, but there are scenarios where JavaScript alone cannot solve the problem. Accessing device-specific hardware (biometric sensors, NFC readers, Bluetooth LE peripherals), integrating existing native SDKs (payment processors, analytics libraries, AR frameworks), or performing computationally intensive operations (image processing, video encoding, ML inference) all require native code. Native modules bridge the gap between JavaScript and the platform layer, allowing you to write Swift/Objective-C (iOS) or Kotlin/Java (Android) code and expose it as a JavaScript API that React Native components can call directly.
Old Bridge vs. New Architecture (TurboModules)
React Native's old bridge serialized all communication between JavaScript and native code as JSON messages sent over an asynchronous queue—introducing latency and preventing synchronous calls. The New Architecture (React Native 0.71+) replaces this with TurboModules and the JavaScript Interface (JSI), enabling direct synchronous calls between JavaScript and native code without JSON serialization. TurboModules are lazy-loaded (initialized only when first accessed, reducing startup time) and type-safe (using CodeGen to generate C++ bindings from Flow/TypeScript specs). For new projects, always target the New Architecture with TurboModules.
Creating a Native Module for Android (Kotlin)
Create a Kotlin class implementing `ReactContextBaseJavaModule`: `class DeviceInfoModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext)`. Override `getName()` to return the module name visible to JavaScript. Annotate exposed methods with `@ReactMethod`: `@ReactMethod fun getDeviceName(promise: Promise) { promise.resolve(Build.MODEL) }`. Create a `ReactPackage` implementation that returns your module in `createNativeModules()`. Register the package in `MainApplication.kt`'s `getPackages()` method. In JavaScript, import via `NativeModules.DeviceInfoModule.getDeviceName()`. Use `Promise` for async results and `Callback` for one-shot callbacks.
Creating a Native Module for iOS (Swift)
Create a Swift class: `@objc(DeviceInfoModule) class DeviceInfoModule: NSObject`. Create a bridging header and an Objective-C file with `RCT_EXTERN_MODULE(DeviceInfoModule, NSObject)` and `RCT_EXTERN_METHOD(getDeviceName:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)`. In the Swift class, implement: `@objc func getDeviceName(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { resolve(UIDevice.current.name) }`. Add `@objc static func requiresMainQueueSetup() -> Bool { return false }` if the module doesn't need main thread initialization. The JavaScript API is identical to the Android module.
Sending Events from Native to JavaScript
Native modules often need to push data to JavaScript—sensor readings, download progress, or lifecycle events. On Android, extend `ReactContextBaseJavaModule` and call `reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java).emit("onSensorData", params)`. On iOS, subclass `RCTEventEmitter`, override `supportedEvents()` to return an array of event names, and call `sendEvent(withName: "onSensorData", body: ["value": 42])`. In JavaScript, subscribe using `NativeEventEmitter`: `const emitter = new NativeEventEmitter(NativeModules.SensorModule); const sub = emitter.addListener('onSensorData', (data) => { ... })`. Always call `sub.remove()` on component unmount to prevent memory leaks.
Transform Your Publishing Workflow
Our experts can help you build scalable, API-driven publishing systems tailored to your business.
Native UI Components: ViewManagers
Beyond modules (which expose methods), you can expose native views as React components. On Android, create a `SimpleViewManager
Building TurboModules with the New Architecture
TurboModules require a spec file (TypeScript or Flow) that defines the module's interface: `export interface Spec extends TurboModule { getDeviceName(): Promise
Testing, Debugging, and Cross-Platform Consistency
Maintain API parity between Android and iOS modules—same method names, same parameter types, same return types. Create a TypeScript wrapper that imports `NativeModules` and provides typed, platform-agnostic methods with runtime checks: `const DeviceInfo = Platform.OS === 'ios' ? NativeModules.DeviceInfoModule : NativeModules.DeviceInfoModule`. Write unit tests for native code using XCTest (iOS) and JUnit (Android). For integration testing, use Detox or Appium to verify the JavaScript-to-native round trip. Debug native code using Xcode's debugger (iOS) and Android Studio's debugger (Android) while the React Native app is running—set breakpoints in native methods to trace execution.




