Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

How Custom Native Modules Work in React Native

How Custom Native Modules Work

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

This chapter does NOT write a custom native module (that would require Xcode/Android Studio and the "bare workflow"/expo prebuild, beyond the scope of this tutorial) – it explains CONCEPTUALLY what happens when a library like expo-camera or @react-native-async-storage/async-storage connects JavaScript to native Swift/Kotlin code, so you can READ and MAKE SENSE of someone else's native module code.

When do you even need your own native module?

Almost everything a typical app needs (camera, location, storage, notifications) already exists as a ready-made Expo/community library – useCart/cartStore in our project, for instance, NEVER touch native stuff directly, they use the ready-made AsyncStorage library. You only need YOUR OWN native module for: (1) platform-specific SDKs without an existing JS wrapper (e.g. a company's own payment SDK), (2) extremely performance-critical code better placed directly in Swift/Kotlin/C++, or (3) access to native behavior React Native itself doesn't expose.

The TurboModule specification

In the new architecture (chapter 8), a native module starts with a TypeScript "spec" file – this is EXACTLY the file you're most likely to encounter in someone else's code, even if you never write a module yourself:

// NativeMyModule.ts - purely declarative, no implementation code
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getBatteryLevel(): number;
  showToast(message: string): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');

From this TypeScript file, React Native AUTOMATICALLY generates C++ code at build time that bridges to the native implementation – you write the interface ONCE in TypeScript, and the native code on both platforms must conform to it exactly.

The native side: conceptual, not meant to be typed out

On iOS (Swift) and Android (Kotlin), one class each implements the methods defined in the spec – JSI then calls these methods DIRECTLY, without the old JSON bridge. The return type getBatteryLevel(): number in the spec means CONCRETELY: the native Swift/Kotlin implementation MUST be able to return a number synchronously – unlike the old bridge system, where practically EVERYTHING had to go through a promise/callback.

Back to our project: why AsyncStorage is fast

Now we can explain WHY AsyncStorage.getItem(...) from chapter 1/2 is performant, even though it calls native filesystem code: the modern version of the library implements EXACTLY this TurboModule pattern – a TypeScript spec file, native Swift/Kotlin implementations, connected via generated C++ code through JSI. As users of the library, we see NONE of this beyond the perfectly ordinary getItem/setItem API – that's exactly the point of a good abstraction.

Reading someone else's native module code: what to look for

  • A *.ts file with extends TurboModule – that IS the public API contract, independent of the implementation.
  • An ios/ folder with .swift/.m files and an android/ folder with .kt/.java files – the actual, platform-specific implementations.
  • A *.podspec file (iOS) or build.gradle (Android) – the build configuration telling the respective native build system how to compile the module.

Tipp: The practical value of this chapter for your day-to-day work: when a community library doesn't behave as expected, you can now search its GitHub repository specifically for the Spec file to understand WHICH methods/values are actually exchanged between JS and the native side – often more informative than the plain README documentation, especially for rarely maintained packages.