From react-native
Guides building and integrating native modules in React Native, covering Turbo Modules, Swift/iOS and Kotlin/Android bridging, and platform APIs.
npx claudepluginhub thebushidocollective/han --plugin react-nativeThis skill is limited to using the following tools:
Use this skill when creating custom native modules, integrating third-party native libraries, or accessing platform-specific functionality not available through JavaScript.
Handles platform-specific code in React Native for iOS and Android using Platform API, platform files, components, native modules, and cross-platform best practices.
Guides creation of React Native native modules using Expo Modules API, Turbo Modules, or Fabric Components with TypeScript APIs, Swift iOS, and Kotlin Android implementations.
Guides React Native development for cross-platform iOS/Android apps, covering components, navigation, native modules, platform-specific code, and performance optimization.
Share bugs, ideas, or general feedback.
Use this skill when creating custom native modules, integrating third-party native libraries, or accessing platform-specific functionality not available through JavaScript.
Native modules bridge JavaScript and native code:
JavaScript Layer
↕ (Bridge)
Native Layer (iOS/Android)
Turbo Modules provide better performance with type safety:
// NativeMyModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getString(value: string): Promise<string>;
getNumber(value: number): number;
getBoolean(value: boolean): boolean;
getArray(value: Array<any>): Array<any>;
getObject(value: Object): Object;
}
export default TurboModuleRegistry.getEnforcing<Spec>('MyModule');
import { NativeModules } from 'react-native';
const { MyModule } = NativeModules;
// Call native method
async function callNativeMethod() {
try {
const result = await MyModule.getString('Hello from JS');
console.log(result);
} catch (error) {
console.error('Native module error:', error);
}
}
Create a native module in Swift:
// MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: NSObject {
@objc
func getString(_ value: String,
resolver: @escaping RCTPromiseResolveBlock,
rejecter: @escaping RCTPromiseRejectBlock) {
// Process value
let result = "Processed: \(value)"
resolver(result)
}
@objc
func getNumber(_ value: NSNumber) -> NSNumber {
let doubled = value.doubleValue * 2
return NSNumber(value: doubled)
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
// MyModule.m (Bridge file)
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(getString:(NSString *)value
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getNumber:(nonnull NSNumber *)value)
@end
Create a native module in Kotlin:
// MyModule.kt
package com.myapp
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "MyModule"
}
@ReactMethod
fun getString(value: String, promise: Promise) {
try {
val result = "Processed: $value"
promise.resolve(result)
} catch (e: Exception) {
promise.reject("ERROR", e.message)
}
}
@ReactMethod
fun getNumber(value: Double): Double {
return value * 2
}
}
// MyModulePackage.kt
package com.myapp
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class MyModulePackage : ReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(MyModule(reactContext))
}
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> {
return emptyList()
}
}
Create a type-safe wrapper:
// MyModule.ts
import { NativeModules, Platform } from 'react-native';
interface MyModuleInterface {
getString(value: string): Promise<string>;
getNumber(value: number): number;
getBoolean(value: boolean): boolean;
}
const LINKING_ERROR =
`The package 'react-native-my-module' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- Run 'pod install'\n", default: '' }) +
'- Rebuild the app';
const MyModule: MyModuleInterface = NativeModules.MyModule
? NativeModules.MyModule
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
},
}
);
export default MyModule;
Send events from native to JavaScript:
// iOS - MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: RCTEventEmitter {
override func supportedEvents() -> [String]! {
return ["onDataReceived"]
}
@objc
func startListening() {
// Simulate receiving data
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.sendEvent(withName: "onDataReceived",
body: ["data": "Hello from native!"])
}
}
}
// Android - MyModule.kt
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private fun sendEvent(eventName: String, params: WritableMap?) {
reactApplicationContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
@ReactMethod
fun startListening() {
val params = Arguments.createMap()
params.putString("data", "Hello from native!")
sendEvent("onDataReceived", params)
}
}
// JavaScript
import { NativeEventEmitter, NativeModules } from 'react-native';
import { useEffect } from 'react';
function useNativeEvent() {
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.MyModule);
const subscription = eventEmitter.addListener('onDataReceived', (event) => {
console.log('Received from native:', event.data);
});
NativeModules.MyModule.startListening();
return () => subscription.remove();
}, []);
}
// JavaScript API
interface CameraModule {
takePicture(): Promise<string>; // Returns image URI
requestPermissions(): Promise<boolean>;
}
// iOS Implementation
import UIKit
import AVFoundation
@objc(CameraModule)
class CameraModule: NSObject {
@objc
func requestPermissions(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
AVCaptureDevice.requestAccess(for: .video) { granted in
resolve(granted)
}
}
@objc
func takePicture(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
// Implement camera capture
// Return image URI
resolve("file:///path/to/image.jpg")
}
}
// Android Implementation
class CameraModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
@ReactMethod
fun requestPermissions(promise: Promise) {
// Check and request camera permission
val hasPermission = ContextCompat.checkSelfPermission(
reactApplicationContext,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
promise.resolve(hasPermission)
}
@ReactMethod
fun takePicture(promise: Promise) {
// Implement camera capture
promise.resolve("file:///path/to/image.jpg")
}
}
// JavaScript API
interface BiometricModule {
authenticate(reason: string): Promise<{ success: boolean; error?: string }>;
isAvailable(): Promise<boolean>;
}
// iOS Implementation
import LocalAuthentication
@objc(BiometricModule)
class BiometricModule: NSObject {
@objc
func isAvailable(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let context = LAContext()
var error: NSError?
let available = context.canEvaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
error: &error
)
resolve(available)
}
@objc
func authenticate(_ reason: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let context = LAContext()
context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: reason
) { success, error in
if success {
resolve(["success": true])
} else {
resolve(["success": false, "error": error?.localizedDescription ?? ""])
}
}
}
}
// JavaScript API
interface DeviceInfoModule {
getDeviceId(): string;
getDeviceName(): string;
getSystemVersion(): string;
getBatteryLevel(): Promise<number>;
}
// iOS Implementation
import UIKit
@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject {
@objc
func getDeviceId() -> String {
return UIDevice.current.identifierForVendor?.uuidString ?? ""
}
@objc
func getDeviceName() -> String {
return UIDevice.current.name
}
@objc
func getSystemVersion() -> String {
return UIDevice.current.systemVersion
}
@objc
func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
UIDevice.current.isBatteryMonitoringEnabled = true
let level = UIDevice.current.batteryLevel
resolve(level)
}
}
// Custom native view
import { requireNativeComponent, ViewProps } from 'react-native';
interface MapViewProps extends ViewProps {
region: {
latitude: number;
longitude: number;
latitudeDelta: number;
longitudeDelta: number;
};
onRegionChange?: (event: any) => void;
}
export const MapView = requireNativeComponent<MapViewProps>('MapView');
// Usage
<MapView
region={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
onRegionChange={(event) => console.log(event.nativeEvent)}
/>
// Bad - Blocking main thread
@objc
func heavyComputation(_ value: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let result = performHeavyWork(value) // Blocks UI
resolve(result)
}
// Good - Use background thread
@objc
func heavyComputation(_ value: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
DispatchQueue.global(qos: .userInitiated).async {
let result = self.performHeavyWork(value)
resolve(result)
}
}
// Bad - No error handling
@ReactMethod
fun readFile(path: String, promise: Promise) {
val content = File(path).readText()
promise.resolve(content)
}
// Good - Proper error handling
@ReactMethod
fun readFile(path: String, promise: Promise) {
try {
val file = File(path)
if (!file.exists()) {
promise.reject("FILE_NOT_FOUND", "File does not exist")
return
}
val content = file.readText()
promise.resolve(content)
} catch (e: Exception) {
promise.reject("READ_ERROR", e.message, e)
}
}
// Bad - Strong reference cycle
class MyModule: NSObject {
var timer: Timer?
@objc
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.doSomething() // Strong reference to self
}
}
}
// Good - Weak reference
class MyModule: NSObject {
var timer: Timer?
@objc
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.doSomething()
}
}
deinit {
timer?.invalidate()
}
}
// Bad - Synchronous network call
@ReactMethod
fun fetchData(url: String): String {
return URL(url).readText() // Blocks thread
}
// Good - Asynchronous with promise
@ReactMethod
fun fetchData(url: String, promise: Promise) {
Thread {
try {
val data = URL(url).readText()
promise.resolve(data)
} catch (e: Exception) {
promise.reject("FETCH_ERROR", e.message)
}
}.start()
}