Creating Custom Native Modules for React Native

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

React Native empowers developers to build cross-platform mobile apps using JavaScript and declarative UI—but sometimes you need access to a platform-specific feature or optimized native code. That’s where custom native modules come in. By writing a thin bridge between native (Java/Objective-C/Swift) and JavaScript, you can leverage device APIs or third-party SDKs not exposed by React Native out of the box. In this guide, you’ll learn why and when to build native modules, how to set up your environment, and step-by-step instructions for both Android and iOS, plus tips on bridging, error handling, and best practices for a robust integration.

Why Create Custom Native Modules?

  • Access Platform APIs
    Some APIs—like advanced biometrics, background services, or low-level sensors—aren’t yet supported by React Native’s core or community packages.
  • Performance-Critical Code
    Offload heavy computation (e.g., image processing, cryptography) to native code for speed and memory efficiency.
  • Third-Party SDK Integration
    Many enterprise or analytics SDKs ship only with native bindings; modules let you wrap them for JS usage.
  • Incremental Migration
    In a brownfield app, you can gradually replace parts of a native codebase with React Native while still calling existing modules.

Setting Up Your Environment

  1. React Native Project bashCopyEditnpx react-native init NativeModuleDemo cd NativeModuleDemo
  2. Android Prerequisites
    • Android Studio with SDK platforms and NDK
    • JAVA_HOME and Android SDK paths configured
  3. iOS Prerequisites
    • Xcode ≥ 12
    • CocoaPods installed (gem install cocoapods)
  4. Directory Structure cssCopyEditNativeModuleDemo/ ├── android/ ├── ios/ └── src/ └── NativeModules/

Android: Creating a Native Module

1. Write the Java/Kotlin Module

In android/app/src/main/java/com/nativemoduledemo/ExampleModule.kt:

kotlinCopyEditpackage com.nativemoduledemo

import com.facebook.react.bridge.*

class ExampleModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

  override fun getName() = "ExampleModule"

  @ReactMethod
  fun multiply(a: Int, b: Int, promise: Promise) {
    try {
      val result = a * b
      promise.resolve(result)
    } catch (e: Exception) {
      promise.reject("ERROR", e)
    }
  }
}

2. Register the Module in a Package

In ExamplePackage.kt:

kotlinCopyEditpackage com.nativemoduledemo

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

class ExamplePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf(ExampleModule(reactContext))
}
override fun createViewManagers(reactContext: ReactApplicationContext) = emptyList<ViewManager>()
}

3. Add the Package to MainApplication

In android/app/src/main/java/com/nativemoduledemo/MainApplication.java:

javaCopyEditimport com.nativemoduledemo.ExamplePackage; // add import

@Override
protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList(
    new MainReactPackage(),
    new ExamplePackage() // register here
  );
}

iOS: Creating a Native Module

1. Write the Objective-C/Swift Module

Objective-C (ExampleModule.m/.h)

ios/NativeModuleDemo/ExampleModule.h:

objcCopyEdit#import <React/RCTBridgeModule.h>

@interface ExampleModule : NSObject <RCTBridgeModule>
@end

ios/NativeModuleDemo/ExampleModule.m:

objcCopyEdit#import "ExampleModule.h"

@implementation ExampleModule

RCT_EXPORT_MODULE();

RCT_REMAP_METHOD(multiply,
                 multiplyWithA:(NSInteger)a b:(NSInteger)b resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{
  @try {
    NSInteger result = a * b;
    resolve(@(result));
  } @catch (NSError *error) {
    reject(@"ERROR", @"Error multiplying", error);
  }
}

@end

Swift (Optional)

Enable bridging by adding SwiftExample.swift and updating the bridging header.

2. Register the Module

CocoaPods auto-linking in RN ≥ 0.60 will detect RCTBridgeModule implementations. Otherwise, add to AppDelegate.m:

objcCopyEdit#import <React/RCTBridgeModule.h>

Bridging to JavaScript

In src/NativeModules/index.js:

jsCopyEditimport { NativeModules } from 'react-native';
const { ExampleModule } = NativeModules;

export default {
  multiply: (a, b) => ExampleModule.multiply(a, b),
};

Usage in your React components:

jsxCopyEditimport React, { useEffect, useState } from 'react';
import { Button, Text, View } from 'react-native';
import ExampleModule from './src/NativeModules';

export default function App() {
  const [result, setResult] = useState(null);

  const handleMultiply = async () => {
    try {
      const value = await ExampleModule.multiply(6, 7);
      setResult(value);
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <View style={{padding: 20}}>
      <Button title="Multiply 6 × 7" onPress={handleMultiply} />
      {result !== null && <Text>Result: {result}</Text>}
    </View>
  );
}

Testing and Error Handling

  • Unit Tests: Write Jest mocks for native modules using jest.mock('react-native', ...).
  • Runtime Checks: Validate module existence and method availability before calling.
  • Promise Handling: Always handle rejections to prevent unhandled promise errors.

Best Practices

  • Keep Module Surface Small: Export focused methods; avoid large object graphs across the bridge.
  • Batch Calls: Combine multiple native invocations into one call if possible to minimize bridge overhead.
  • Threading: Perform heavy work on background threads in native code to avoid JS blocking.
  • Documentation: Document native method signatures and promise behavior for JS consumers.

Conclusion

Custom native modules let you fill gaps in React Native’s cross-platform APIs, integrate third-party SDKs, and optimize performance-critical code. By following the patterns above—setting up packages, writing bridge code, and consuming from JavaScript—you can build maintainable, performant modules for both Android and iOS. Start with small, focused modules and evolve your codebase as your app’s requirements grow.

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts