React Native — Turbo Modules guide for iOS

Tim Claes
4 min readApr 14, 2023

--

This is actually my very first (and who knows, maybe also my last) article here on Medium, and at this point it’s merely to document the steps needed to implement a React Native Turbo Module for iOS. I had troubles finding a decent walkthrough, and also the official docs aren’t very good at point of writing imo. So I’m writing this article for my own future reference, and hopefully you find it useful too.

Setup used:
- RN v0.71
- Vanille RN project set up through the RN CLI (official docs)

Starting with v0.71, the RN team really simplified the whole Turbo Modules setup. If you’re on an older version, additional code with be required and this guide will be insufficient.

Step 1: TS Spec file

This is the TypeScript (or Flow) interface which defines the properties and functions the native code will provide your React Native app. Let’s create this file inside the RN project, inside a new turbomodules folder. By convention the file name needs to start with Native, and for this example we’ll call it NativeTurboModule.ts.

Note: obviously it’s possible (and probably recommended) to create a separate node package for your modules, but for simplicity we’re creating our Turbo Module inline in the RN project.

// YourProject/turbomodules/NativeTurboModule.ts
import type {TurboModule} from 'react-native';
import {TurboModuleRegistry} from 'react-native';

export interface Spec extends TurboModule {
// Define your methods and properties below
}

export default TurboModuleRegistry.getEnforcing<Spec>(
'MyTurboModule', // this is the name of your TurboModule
);

Now we need to define our custom interface inside this file, let’s we’ll go with a simple Hello World example for now:

// YourProject/turbomodules/NativeTurboModule.ts
...
export interface Spec extends TurboModule {
// Define your methods and properties here:
getTurboHelloWorld(value: string): string; // Only new line
}
...

Step 2: Codegen

Codegen is the tool which will generate the C++ and Objective-C++ version of your TS interface. Codegen part of the RN library, but some additional configuration inside your project’s package.json is required:

  // YourProject/package.json
...
"codegenConfig": {
"name": "NativeTurboModuleSpec",
"type": "modules",
"jsSrcsDir": "turbomodules",
}
...

Next we need a .podspec file, since an additional Pod will be created for the Turbo Module. So next to our TS spec file, create another new file.

# YourProject/turbomodules/TurboPodSpec.podspec
require "json"

package = JSON.parse(File.read(File.join(__dir__, "../package.json")))

Pod::Spec.new do |s|
s.name = "TurboPodSpec"
s.version = "version"
s.summary = "summary"
s.description = "description"
s.homepage = "homepage"
s.license = "license"
s.platforms = { :ios => "12.4" }
s.authors = "author"
s.source = { :git => '' }
s.source_files = "ios/**/*.{h,m,mm,swift}"
s.pod_target_xcconfig = {
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
install_modules_dependencies(s)
end

And then we simply need to add this Pod to our project’s Podfile, and we’ll check to make sure the new RN architecture is enabled when adding it.

# YourProject/ios/Podfile
use_react_native!(
...
)

# Add this below the use_react_native! directive
if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
pod 'TurboPodSpec', :path => "./../turbomodules"
end

This is all we need for Codegen to be able to generate the native interface for iOS. When installing the pods for the new architecture, Codegen is run automatically as part of the process. So run the command below inside the ios folder of your RN project:

RCT_NEW_ARCH_ENABLED=1 bundle exec pod install

Inside the ios folder, you’ll now find a brand new build folder containing a bunch of C++ and Objective-C++ files and code.

Step 3: Native Code

Everything is now in place to start writing your native code. Add an Objective-C++ header and implementation (.mm file) file in XCode. A normal Objective-C (.m file) implementation file isn’t enough, since it will reference C++ code. Let’s start off with the header file:

// YourProject/ios/MyTurboModule.h
// Name needs to be the same as defined inside your TS Spec file
#import <NativeTurboModuleSpec/NativeTurboModuleSpec.h>

@interface MyTurboModule: NSObject <NativeTurboModuleSpec>
@end

And next the interesting part, inside the implemention file:

// YourProject/ios/MyTurboModule.mm
#import "MyTurboModule"

@implementation MyTurboModule

-(std::shared_ptr<facebook::react::TurboModule>)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeTurboModuleSpecJSI>(params);
}

@end

Above we’re registering the native module. If you keep it like this, XCode should warn you that the implementation isn’t complete, since it’s missing the function defined in our interface. So let’s add it:

// YourProject/ios/MyTurboModule.mm
#import "MyTurboModule"

@implementation TurboPill

-(std::shared_ptr<facebook::react::TurboModule>)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeTurboModuleSpecJSI>(params);
}

- (NSString *)getTurboHelloWorld:(NSString *)value {
return [NSString stringWithFormat:@"Hello world from turbo module: %@", value];
}

@end

Since we’re implementing all of this inline, we still need to import the code somewhere. So let’s import it inside our AppDelegate.mm:

#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>

#import "MyTurboModule" // Only addition!

// rest of delegate ...

Final step: React Native!

And now everything is in place to start testing our Turbo Module. Head over to App.tsx, import our module and test it out:

import TheModule from './turbomodules/NativeTurboModule';

function App(): JSX.Element {

return (
<Text>{TheModule.getTurboHelloWorld("Does this thing work?")}</Text>
);
}

That was it, thanks for reading and I hope it was helpful!

--

--