With the release of React Native 0.79 and React 19, bridging native modules to JavaScript has become even more seamless using the TurboModules system. In this guide, we’ll walk through how to create a Swift-based TurboModule and expose native functionality to your React Native app.
Project Setup
We assume you're using the latest versions:
{
"dependencies": {
"react": "19.0.0",
"react-native": "0.79.3"
}
}
Directory Structure
Your project should resemble:
yourapp/
├── ios/
│ ├── NativeMessageModule/
│ │ ├── NativeMessageModule.swift
│ │ └── NativeMessageModule-Swift.m
├── specs/
│ └── NativeMessageModule.ts
├── App.js
├── package.json
└── ...
Define the TurboModule Interface
Create specs/NativeMessageModule.ts:
import {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
import {TurboModuleRegistry} from 'react-native';
export interface Spec extends TurboModule {
sendMessage(message: string): void;
getMessage(): Promise<string>;
sendWithCallback(callback: (response: string) => void): void;
addListener(eventName: string): void;
removeListeners(count: number): void;
startSendingEvents(): void;
}
export default TurboModuleRegistry.getEnforcing<Spec>('NativeMessageModule');
✅ All native TurboModules must begin with the Native prefix and match the registered module name exactly (NativeMessageModule here).
Configure Codegen
In your package.json, add:
"codegenConfig": {
"name": "NativeMessageModule",
"type": "modules",
"jsSrcsDir": "specs",
"android": {
"javaPackageName": "com.turbomodules" #Replace this with your packagename
}
}
Then, inside the ios folder:
cd ios
bundle install
bundle exec pod install
Implement Native Code in Swift
1. Native Bridge Header (Objective-C)
Create ios/NativeMessageModule/NativeMessageModule-Swift.m:
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(NativeMessageModule, RCTEventEmitter)
RCT_EXTERN_METHOD(sendMessage:(NSString *)message)
RCT_EXTERN_METHOD(getMessage:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(sendWithCallback:(RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(startSendingEvents)
@end
2. Swift Implementation
Create ios/NativeMessageModule/NativeMessageModule.swift:
import Foundation
import React
@objc(NativeMessageModule)
class NativeMessageModule: RCTEventEmitter {
private var hasListeners = false
override static func requiresMainQueueSetup() -> Bool {
return true
}
override func supportedEvents() -> [String]! {
return ["onTimerTick"]
}
override func startObserving() {
hasListeners = true
}
override func stopObserving() {
hasListeners = false
}
@objc
func sendMessage(_ message: String) {
NSLog("[iOS TurboModule] Received from JS: %@", message)
}
@objc
func getMessage(_ resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock) {
let response = "Hello from iOS TurboModule"
resolve(response)
}
@objc
func sendWithCallback(_ callback: @escaping RCTResponseSenderBlock) {
callback(["Callback response from iOS TurboModule"])
}
@objc
func startSendingEvents() {
guard hasListeners else { return }
for i in 1...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) { [weak self] in
guard let self = self, self.hasListeners else { return }
let eventData = ["message": "iOS TurboModule Event at \(Date().timeIntervalSince1970)"]
self.sendEvent(withName: "onTimerTick", body: eventData)
}
}
}
}
Reinstall Pods After Codegen
After creating Swift files and configuring the bridging header:
cd ios
bundle install
bundle exec pod install
React Native Integration
Use the TurboModule in your App.js:
import React, {useState, useEffect} from 'react';
import {
View,
Button,
Text,
StyleSheet,
NativeEventEmitter,
} from 'react-native';
import NativeMessageModule from './specs/NativeMessageModule';
const App = () => {
const [receivedMessage, setReceivedMessage] = useState('');
const [lastSentMessage, setLastSentMessage] = useState('');
const [callbackMsg, setCallbackMsg] = useState('');
const [eventMsg, setEventMsg] = useState('');
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeMessageModule);
const subscription = eventEmitter.addListener('onTimerTick', event => {
setEventMsg(event.message);
});
return () => subscription.remove();
}, []);
return (
<View style={styles.container}>
<Text style={styles.title}>Bridging Example</Text>
<Button title="Send Message to Native" onPress={() => {
const msg = 'Hello from React Native';
NativeMessageModule.sendMessage(msg);
setLastSentMessage(msg);
}} />
<Text style={styles.message}>Last sent: {lastSentMessage}</Text>
<Button title="Get Message from Native" onPress={async () => {
const msg = await NativeMessageModule.getMessage();
setReceivedMessage(msg);
}} />
<Text style={styles.message}>Received: {receivedMessage}</Text>
<Button title="Use Callback" onPress={() => {
NativeMessageModule.sendWithCallback(msg => setCallbackMsg(msg));
}} />
<Text>Received: {callbackMsg}</Text>
<Button title="Trigger Native Event" onPress={() => {
NativeMessageModule.startSendingEvents();
}} />
<Text>Received: {eventMsg}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {flex: 1, justifyContent: 'center', padding: 16},
title: {fontSize: 24, fontWeight: 'bold', marginBottom: 32, textAlign: 'center'},
message: {marginTop: 8, fontSize: 14, color: '#666'},
});
export default App;
📌 Summary
In this blog, we:
✅ Defined a TurboModule spec in TypeScript
✅ Created Swift native module logic compatible with the New Architecture
✅ Configured the Objective-C Bridging Header
✅ Handled Promises, Callbacks, and native event emissions
✅ Used the module in React Native with hooks and state

Top comments (0)