Claude Code Plugins

Community-maintained marketplace

Feedback

jutsu-react-native:react-native-native-modules

@TheBushidoCollective/han
38
0

Use when building or integrating native modules in React Native. Covers creating native modules, Turbo Modules, bridging native code, and accessing platform-specific APIs.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name jutsu-react-native:react-native-native-modules
description Use when building or integrating native modules in React Native. Covers creating native modules, Turbo Modules, bridging native code, and accessing platform-specific APIs.
allowed-tools Read, Write, Edit, Bash, Grep, Glob

React Native Native Modules

Use this skill when creating custom native modules, integrating third-party native libraries, or accessing platform-specific functionality not available through JavaScript.

Key Concepts

Native Modules Overview

Native modules bridge JavaScript and native code:

JavaScript Layer
      ↕ (Bridge)
Native Layer (iOS/Android)

Turbo Modules (Modern Approach)

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');

Calling Native Code from JS

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);
  }
}

Best Practices

iOS Native Module (Swift)

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

Android Native Module (Kotlin)

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()
    }
}

TypeScript Wrapper

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;

Native Events

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();
  }, []);
}

Common Patterns

Camera Access

// 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")
    }
}

Biometric Authentication

// 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 ?? ""])
      }
    }
  }
}

Device Info Module

// 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)
  }
}

Native UI Component

// 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)}
/>

Anti-Patterns

Don't Block the Main Thread

// 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)
  }
}

Don't Forget Error Handling

// 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)
    }
}

Don't Leak Memory

// 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()
    }
}

Don't Use Synchronous Operations

// 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()
}

Related Skills

  • react-native-platform: Platform-specific code patterns
  • react-native-components: Integrating native components
  • react-native-performance: Native performance optimization