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

Quick Example

An Expo module exposing a synchronous function, an async function, and an event:

Core Concepts

When You Need Native Code

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:

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:

  1. Write a TypeScript spec extending TurboModule.
  2. Configure codegenConfig in package.json.
  3. Codegen generates native interfaces (Objective-C++ and Java or Kotlin, plus C++ glue).
  4. 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:

Consider sizing and layout (Yoga measures the view), prop updates, event emission, and accessibility for native views.

Threading

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

References