React Native is amazing. It lets you write JavaScript to describe your UI, use React libraries to handle common use cases, and install React Native-specific libraries to interact with platform APIs. You can rely on experts to handle the complex native parts for you.
But what happens when there’s no library for the native API you need? Or when you’re working with a proprietary native SDK? Suddenly, you’re forced to leave the comfort of JavaScript and dive into native code.
How hard can it be? React and React Native are well-documented, and native code is designed to run directly on the device. Surely, creating your own custom library can’t be that difficult. Many library developers have done it before, and there must be plenty of resources, right? Just ask GPT for some sweet implementations, and you’re good to go.
Well, not quite. Let’s talk about why.
Closing the gap
In React Native, you can use any JavaScript library as long as it relies on APIs supported by your JavaScript engine of choice, such as Hermes. Creating a JavaScript-only library is straightforward.
But when you need to sprinkle in some native code, you must bridge the gap between JavaScript and the native world. React Native provides APIs to handle this bridging, but here’s the catch: there’s more than one API, and each comes with its own quirks.
Native Modules: The legacy API
The Native Modules API is the original way to bridge JavaScript and native code in React Native. It uses a literal “bridge” component to handle communication and type conversions. However, this approach has some significant downsides:
- Performance: Type conversions rely on JSON serialization and deserialization, which can be slow and are mostly asynchronous.
- Swift Support: On iOS, there’s no first-class Swift support. You need to write Objective-C bindings to call Swift code.
- Out-of-Sync APIs: There’s no guarantee that a JavaScript API is implemented in native code, which can lead to inconsistencies and bugs.
Despite these limitations, Native Modules are easy to get started with. You can follow the official React Native docs or run:
However, Native Modules are now considered legacy, as React Native has moved to the new architecture that’s enabled by default.
Turbo Modules: The next generation
With the new React Native architecture comes Turbo Modules, the next-generation API for bridging JavaScript and native code. Turbo Modules replace the bridge component with the JavaScript Interface (JSI), which allows direct interaction with the JavaScript engine. This brings several benefits:
- Performance: Turbo Modules are faster because they avoid the overhead of JSON serialization.
- Sync APIs: JSI enables synchronous APIs, which weren’t possible with the old bridge.
- C++ Power: JSI allows you to inject C++ instances directly into JavaScript memory.
But there’s a catch: working with Turbo Modules isn’t always pleasant. JSI requires C++, which means you’ll need boilerplate-heavy code to create JSI-compatible C++ classes. If you want to use more developer-friendly platform languages like Kotlin or Swift, you’ll need even more boilerplate.
To help with this, the React Native team created React Native Codegen, a tool that generates the scaffolding code for Turbo Modules. However, even with Codegen, Swift isn’t supported out of the box, and setting up Turbo Modules can be tricky. You can get started with Turbo Modules by following the official React Native docs or running:
Nitro Modules: The future of Native Modules
What if you could have first-class support for Swift and Kotlin, minimal boilerplate, and great abstractions so you can focus on solving your problem? That’s where Nitro Modules come in.
Nitro Modules offer:
- Performance: They’re as fast as (or faster than) other APIs, thanks to JSI.
- First-Class Swift and Kotlin Support: No need for Objective-C bindings or extra boilerplate.
- Ergonomics: Clever abstractions make Nitro Modules easy to use.
The only downside? Nitro Modules are still relatively new. But they’re already showing promising adoption in the community. To get started with Nitro Modules, check out the Nitro Modules documentation or run:
Getting up to speed
We’ve covered some of the available APIs, but bridging the gap between JavaScript and native code is only part of the problem. There are other concerns you’ll need to address, such as:
- Creating an example app to test your code.
- Setting up a CI pipeline for automated checks and cached native builds.
- Supporting TypeScript, ESModules, and CommonJS.
- Automating the library build and release process.
- Adding documentation and contribution guidelines.
- Creating templates for GitHub issues and pull requests.
And the list goes on. This list is a small subset of problems we’ve been addressing with create-react-native-library, and we’ve recently added support for the Nitro Modules API! It automates all the minor but time-consuming issues, so you can focus on writing those sweet native bindings.
We’re also working on an upgrade helper for libraries created with create-react-native-library, similar to React Native Upgrade Helper, so you can keep your libraries up-to-date with the latest React Native releases.
You can try out create-react-native-library and Nitro Modules today by running:
Make sure to check out the documentation for more details.