Claude Code Plugins

Community-maintained marketplace

Feedback
19
0

Create custom React Native native modules for iOS and Android. Use when integrating native SDKs, optimizing performance-critical code, or accessing platform-specific APIs. Trigger words include "native module", "bridge", "native code", "iOS bridge", "Android bridge", "Turbo Module".

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 native-module-helper
description Create custom React Native native modules for iOS and Android. Use when integrating native SDKs, optimizing performance-critical code, or accessing platform-specific APIs. Trigger words include "native module", "bridge", "native code", "iOS bridge", "Android bridge", "Turbo Module".

Native Module Helper

Build custom native modules to bridge JavaScript and native code in React Native.

Quick Start

Native modules expose native functionality to JavaScript. Choose based on React Native version:

  • Legacy Bridge: RN < 0.68 (stable, widely supported)
  • Turbo Modules: RN >= 0.68 (better performance, type-safe)

Instructions

Step 1: Plan Module Interface

Design JavaScript API:

// What you want to call from JS
import { NativeModules } from 'react-native';
const { MyModule } = NativeModules;

// Synchronous
const result = MyModule.getValue();

// Asynchronous (Promise)
const data = await MyModule.fetchData();

// With callback
MyModule.processData(input, (error, result) => {
  if (error) console.error(error);
  else console.log(result);
});

// Event emitter
MyModule.addListener('onUpdate', (event) => {
  console.log(event);
});

Keep bridge calls minimal:

  • Batch operations when possible
  • Avoid frequent small calls
  • Use events for continuous updates

Step 2: Create Module Structure

File structure:

MyModule/
├── ios/
│   ├── MyModule.h
│   ├── MyModule.m (or .swift)
│   └── MyModule-Bridging-Header.h (if Swift)
├── android/
│   └── src/main/java/com/mymodule/
│       ├── MyModulePackage.java
│       └── MyModule.java (or .kt)
├── js/
│   └── NativeMyModule.ts
└── package.json

Step 3: Implement iOS Module

Objective-C (.h file):

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

@interface MyModule : RCTEventEmitter <RCTBridgeModule>
@end

Objective-C (.m file):

#import "MyModule.h"

@implementation MyModule

RCT_EXPORT_MODULE();

// Synchronous method
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getValue)
{
  return @"value";
}

// Async with Promise
RCT_EXPORT_METHOD(fetchData:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  // Perform operation
  if (success) {
    resolve(@{@"data": result});
  } else {
    reject(@"ERROR_CODE", @"Error message", error);
  }
}

// Async with callback
RCT_EXPORT_METHOD(processData:(NSString *)input
                  callback:(RCTResponseSenderBlock)callback)
{
  // Process data
  callback(@[[NSNull null], result]); // [error, result]
}

// Event emitter
- (NSArray<NSString *> *)supportedEvents
{
  return @[@"onUpdate"];
}

- (void)sendUpdate:(NSDictionary *)data
{
  [self sendEventWithName:@"onUpdate" body:data];
}

@end

Step 4: Implement Android Module

Java module:

package com.mymodule;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.modules.core.DeviceEventManagerModule;

public class MyModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext reactContext;

    public MyModule(ReactApplicationContext context) {
        super(context);
        this.reactContext = context;
    }

    @Override
    public String getName() {
        return "MyModule";
    }

    // Synchronous method
    @ReactMethod(isBlockingSynchronousMethod = true)
    public String getValue() {
        return "value";
    }

    // Async with Promise
    @ReactMethod
    public void fetchData(Promise promise) {
        try {
            WritableMap result = Arguments.createMap();
            result.putString("data", "value");
            promise.resolve(result);
        } catch (Exception e) {
            promise.reject("ERROR_CODE", "Error message", e);
        }
    }

    // Async with callback
    @ReactMethod
    public void processData(String input, Callback callback) {
        try {
            String result = process(input);
            callback.invoke(null, result); // error, result
        } catch (Exception e) {
            callback.invoke(e.getMessage(), null);
        }
    }

    // Event emitter
    private void sendEvent(String eventName, WritableMap params) {
        reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, params);
    }
}

Package registration:

package com.mymodule;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyModulePackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new MyModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

Step 5: Link Module

Auto-linking (RN >= 0.60):

Create package.json in module root:

{
  "name": "react-native-my-module",
  "version": "1.0.0",
  "main": "js/index.js",
  "react-native": "js/index.js"
}

Manual linking (if needed):

iOS: Add to Podfile

pod 'MyModule', :path => '../node_modules/react-native-my-module'

Android: Add to settings.gradle

include ':react-native-my-module'
project(':react-native-my-module').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-my-module/android')

Step 6: Create TypeScript Interface

// js/NativeMyModule.ts
import { NativeModules, NativeEventEmitter } from 'react-native';

interface MyModuleInterface {
  getValue(): string;
  fetchData(): Promise<{ data: string }>;
  processData(input: string, callback: (error: string | null, result: string | null) => void): void;
  addListener(eventName: string, listener: (event: any) => void): void;
  removeListeners(count: number): void;
}

const { MyModule } = NativeModules;
const eventEmitter = new NativeEventEmitter(MyModule);

export default MyModule as MyModuleInterface;
export { eventEmitter };

Common Patterns

Threading

iOS (run on background thread):

RCT_EXPORT_METHOD(heavyTask:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // Heavy operation
    NSString *result = [self performHeavyOperation];
    
    dispatch_async(dispatch_get_main_queue(), ^{
      resolve(result);
    });
  });
}

Android (run on background thread):

@ReactMethod
public void heavyTask(Promise promise) {
    new Thread(() -> {
        try {
            String result = performHeavyOperation();
            promise.resolve(result);
        } catch (Exception e) {
            promise.reject("ERROR", e);
        }
    }).start();
}

Data Type Conversion

iOS:

// JS -> Native
NSString *string = [RCTConvert NSString:value];
NSNumber *number = [RCTConvert NSNumber:value];
NSArray *array = [RCTConvert NSArray:value];
NSDictionary *dict = [RCTConvert NSDictionary:value];

// Native -> JS
return @{
  @"string": @"value",
  @"number": @(42),
  @"array": @[@"a", @"b"],
  @"dict": @{@"key": @"value"}
};

Android:

// JS -> Native (automatic)
String string = input;
int number = input;
ReadableArray array = input;
ReadableMap map = input;

// Native -> JS
WritableMap result = Arguments.createMap();
result.putString("string", "value");
result.putInt("number", 42);

WritableArray array = Arguments.createArray();
array.pushString("a");
array.pushString("b");
result.putArray("array", array);

Error Handling

iOS:

RCT_EXPORT_METHOD(riskyOperation:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  NSError *error = nil;
  id result = [self performOperation:&error];
  
  if (error) {
    reject(@"OPERATION_FAILED", error.localizedDescription, error);
  } else {
    resolve(result);
  }
}

Android:

@ReactMethod
public void riskyOperation(Promise promise) {
    try {
        Object result = performOperation();
        promise.resolve(result);
    } catch (Exception e) {
        promise.reject("OPERATION_FAILED", e.getMessage(), e);
    }
}

Advanced

For detailed platform-specific guides:

Troubleshooting

Module not found:

  • Verify package.json configuration
  • Run pod install (iOS) or rebuild (Android)
  • Check module name matches in native code

Methods not available:

  • Ensure RCT_EXPORT_METHOD is used
  • Check method signature matches
  • Rebuild native code

Crashes on method call:

  • Check thread safety
  • Verify data type conversions
  • Add null checks
  • Review error handling

Events not received:

  • Verify supportedEvents (iOS)
  • Check event emitter setup
  • Ensure listeners are added before events fire

Best Practices

  1. Minimize bridge calls: Batch operations, use events for updates
  2. Type safety: Use TypeScript interfaces
  3. Error handling: Always handle errors gracefully
  4. Threading: Move heavy operations off main thread
  5. Memory management: Clean up resources, remove listeners
  6. Testing: Test on both iOS and Android
  7. Documentation: Document API clearly
  8. Versioning: Use semantic versioning