React Native Native Modules
Most React Native apps can be built entirely in JavaScript, using the framework's components and the rich library ecosystem. Eventually, though, you may need something no library provides: a platform SDK (payments, Bluetooth, health data, a proprietary vendor SDK), a performance-critical routine, or a native UI component. Native modules expose native code (Swift or Objective-C on iOS, Kotlin or Java on Android, or shared C++) to JavaScript.
Modern React Native offers two main ways to write them. The Expo Modules API is a concise Swift and Kotlin DSL that works in any React Native app. Turbo Native Modules and Fabric components use Codegen specs, and are the core New Architecture mechanism. Both produce typed, efficient, JSI-backed interfaces.
TL;DR
- Check for an existing library first (React Native Directory, Expo SDK). Writing native code means maintaining it on two platforms.
- Expo Modules API: Swift and Kotlin DSL with functions, async functions, events, views, and lifecycle. It's the simplest path, and works outside Expo-managed apps too.
- Turbo Native Modules: a TypeScript spec plus Codegen, with native implementations; the lowest-level official approach.
- Native UI components: Expo module views, or Fabric native components via Codegen.
- Return Promises for slow work, emit events for streams, and avoid blocking the JS or UI threads.
- Use config plugins to modify native projects reproducibly (permissions, Info.plist, Gradle).
Quick Example
An Expo module exposing a synchronous function, an async function, and an event:
Core Concepts
When You Need Native Code
- Platform APIs without a maintained library: HealthKit and Health Connect, advanced Bluetooth, NFC, background tasks, widgets, App Clips, CarPlay and Android Auto.
- Vendor SDKs that ship only native binaries: payments, identity verification, analytics, device management.
- Performance-critical work: image and audio processing, cryptography, on-device ML inference.
- Custom native UI: maps, video players, camera previews, charts backed by native rendering.
Before writing a module, search React Native Directory and the Expo SDK. Established libraries handle edge cases across OS versions.
Expo Modules API
The Expo Modules API offers a declarative Swift and Kotlin DSL:
Function(synchronous),AsyncFunction(Promise-based),Property,Constant.EventswithsendEvent, plus observing lifecycle hooks.Viewdefinitions withProps and events for native UI components.- Automatic type conversion (records, enums, arrays, typed arrays), and JSI-based performance.
- Works in bare React Native apps (with the
expopackage installed) as well as Expo apps, and supports the New Architecture.
Create one with npx create-expo-module, either as a local module inside your app or as a publishable package.
Turbo Native Modules and Codegen
The React Native core approach:
- Write a TypeScript spec extending
TurboModule. - Configure
codegenConfiginpackage.json. - Codegen generates native interfaces (Objective-C++ and Java or Kotlin, plus C++ glue).
- Implement those interfaces natively and register the module.
Codegen ensures JS and native signatures match. C++ TurboModules let you share one implementation across platforms, which suits cross-platform logic like parsing, crypto, and algorithms.
Native UI Components
To embed a native view in React:
- Expo Modules: define a
Viewwith props and events in the module definition. - Fabric native components: a Codegen component spec plus native view implementations, integrated with Fabric's C++ renderer.
Consider sizing and layout (Yoga measures the view), prop updates, event emission, and accessibility for native views.
Threading
- Synchronous functions run on the JS thread, so they must be fast. Blocking them blocks JavaScript.
- UI work must happen on the main thread (dispatch to it when touching views).
- Slow operations (I/O, network, heavy computation) belong on background queues, returning Promises.
Config Plugins and Native Project Changes
Native modules often require manifest permissions, Info.plist keys, entitlements, Gradle dependencies, or AppDelegate hooks. Expo config plugins apply these changes programmatically during prebuild, so native projects can be regenerated consistently (Continuous Native Generation) rather than edited by hand and drifting. See Expo.
Best Practices
Wrap Native APIs in a Clean TypeScript Interface
Expose a small, typed, JavaScript-friendly API (Promises, typed events, clear errors), and hide platform quirks behind it. Consumers shouldn't need to know which platform they're on.
Keep Behavior Consistent Across Platforms
Define the contract once (spec or types), document platform differences explicitly, and test both implementations with the same scenarios. Divergent behavior is the most common native module bug.
Handle Permissions and Errors Explicitly
Request permissions with clear flows, reject Promises with meaningful error codes (E_PERMISSION_DENIED, E_UNAVAILABLE), and handle unsupported devices and OS versions gracefully.
Test on Both Platforms and Several OS Versions
Native APIs change between iOS and Android releases. Include older supported OS versions in testing, and follow platform deprecation notices.
Common Mistakes
Blocking the JS Thread With Synchronous Native Calls
Doing file I/O or network calls inside a synchronous function freezes all JavaScript. Use async functions and background threads.
Updating UI Off the Main Thread
Touching UIKit or Android views from background threads causes crashes or undefined behavior. Dispatch UI updates to the main thread.
Manual Native Edits in Expo Prebuild Projects
Editing generated ios/ and android/ folders by hand is lost on the next prebuild. Encode native changes in config plugins.
FAQ
Should I use the Expo Modules API or Turbo Native Modules?
For most app developers and many library authors, the Expo Modules API is faster to write and maintain: a concise Swift and Kotlin DSL, events and views built in, and it works in any React Native project. Use Turbo Native Modules (or C++ TurboModules) when you want only React Native core tooling, need C++ shared implementations, or are contributing to libraries that standardize on Codegen.
Can I use native modules with Expo?
Yes. Expo apps can include any native code through development builds and prebuild (Continuous Native Generation). Custom modules can be written with the Expo Modules API, and config plugins handle native project changes. Only Expo Go, the prebuilt sandbox app, is limited to Expo's bundled modules.
How do I send events from native code to JavaScript?
With Expo modules, declare Events(...) and call sendEvent(name, payload). JavaScript subscribes with addListener. With TurboModules, use the event emitter support in the spec, or the platform's event emitter APIs. Start and stop native observers based on listener counts to avoid wasted work.
Do native modules work with the New Architecture?
Modules written with Codegen specs (TurboModules and Fabric components) or the Expo Modules API support the New Architecture. Legacy bridge-based modules may work through the interop layer, but should be migrated for full compatibility.
Related Topics
- React Native — The framework overview
- React Native Architecture — JSI, TurboModules, and Fabric
- Expo — Expo Modules and config plugins
- Swift — iOS native implementation language
- Kotlin — Android native implementation language
- iOS Development — Platform APIs on iOS