Native modules: when JavaScript genuinely isn't enough
Lesson 2 established that JS and native code communicate through the bridge or JSI. Native modules are the actual, concrete thing flowing across that boundary — real Swift/Kotlin (or Objective-C/Java) code, exposed to JavaScript, for the specific capabilities JavaScript alone structurally cannot provide.
Advanced
2 min read
What JavaScript genuinely cannot do on its own
Camera hardware access — needs a native camera API
Biometric authentication — needs Face ID / Touch ID / fingerprint APIs
Bluetooth — needs platform-specific Bluetooth stacks
Background processing — needs OS-level background task APIs
Deep OS integration (widgets,
share sheets, notifications) — needs platform-specific system APIs
JavaScript, running inside React Native's JS engine, has no inherent access to device hardware or deep OS-level APIs at all — every one of these capabilities requires real, platform-specific native code (Swift/Objective-C on iOS, Kotlin/Java on Android) that actually calls the operating system's own APIs, with a native module acting as the bridge exposing that native functionality to JavaScript as a callable API.
Most of the time, someone already wrote the native module — you just install it
npm install react-native-camera
The React Native ecosystem has mature, well-maintained native modules for nearly every common device capability — camera access, biometrics, Bluetooth, push notifications — meaning most real apps never need to write native code directly at all; they install a community (or officially-maintained) package that already wraps the native implementation in a clean JavaScript API. This is directly analogous to how most web development never requires writing browser-engine C++ — someone else already built and exposed the capability through a clean, higher-level interface.
When you'd actually write a native module yourself
A genuinely new device capability with no existing package, a performance-critical operation better done in native code directly (heavy image processing, a custom native UI component with no React Native equivalent), or integrating with an existing native SDK a company already has (a proprietary hardware SDK, a legacy native library) are the real, concrete cases that justify writing a native module from scratch — genuinely real work, requiring actual Swift/Kotlin competence, not something to reach for casually when an existing package would do.
The New Architecture's TurboModules: the JSI-based evolution of native modules
Lesson 2 already established JSI's synchronous, direct-reference model as the New Architecture's core improvement over the bridge. TurboModules are native modules built specifically to take advantage of this — synchronous calls where appropriate, lazy loading (a module is only initialized the first time it's actually used, rather than every native module loading eagerly at app startup), and type-safety generated directly from the module's own specification. A library still built on the old bridge-based native module API still works under the New Architecture (via a compatibility layer), but doesn't get these specific benefits until it's actually migrated to TurboModules.