Mobile Development 15 min read

How to Seamlessly Integrate and Dynamically Deploy React Native Modules in a Native App

This article explains how to reuse host‑app functionality via dependency injection, develop and debug React Native modules with TypeScript, and deploy updates both statically and dynamically using CodePush, while sharing best practices for crash handling and version compatibility.

Baixing.com Technical Team
Baixing.com Technical Team
Baixing.com Technical Team
How to Seamlessly Integrate and Dynamically Deploy React Native Modules in a Native App

External Dependencies

Embedded RN modules often need to call host‑app features such as encrypted API access or user data. Rather than re‑implement these functions, Pegasus adopts a dependency‑injection approach by defining abstract interfaces that RN code depends on, while the host app provides concrete implementations.

The key interface is PEGBaixingAPI, which declares GET and POST methods. The RN side calls this interface, and the host app injects its existing BXAPI implementation.

// Abstract interface definition
@protocol PEGBaixingAPI <NSObject>

@required
- (void)GET:(NSString *)URLString parameters:(nullable NSDictionary<NSString *, id> *)parameters success:(nullable void (^)(id _Nullable responseObject))success failure:(nullable void (^)(NSError *error))failure;
- (void)POST:(NSString *)URLString parameters:(nullable NSDictionary<NSString *, id> *)parameters success:(nullable void (^)(id _Nullable responseObject))success failure:(nullable void (^)(NSError *error))failure;

@end

JavaScript code creates a request object and invokes the bridge module:

const options = {
  method: 'get',
  api: '/v2/users',
  params: {
    id: 1234567890
  }
}

NativeModules.PEGBaixingAPIModule.request(options).then((user) => {
  console.log(user)
}).catch((error) => {
  console.log(error)
})

The bridge module depends only on the abstract PEGBaixingAPI:

@interface PEGBaixingAPIModule : NSObject <RCTBridgeModule>

@property (nonatomic, readonly) id<PEGBaixingAPI> baixingAPI;

+ (instancetype)moduleWithBaixingAPI:(id<PEGBaixingAPI>)baixingAPI;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithBaixingAPI:(id<PEGBaixingAPI>)baixingAPI NS_DESIGNATED_INITIALIZER;

@end

@implementation PEGBaixingAPIModule
RCT_EXPORT_METHOD(request:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
  NSString *method = options[@"method"];
  NSString *path = options[@"api"];
  NSDictionary *parameters = options[@"params"];
  // ... validation and callbacks ...
  if ([method isEqualToString:@"get"]) {
    [self.baixingAPI GET:path parameters:parameters success:successBlock failure:failureBlock];
  } else if ([method isEqualToString:@"post"]) {
    [self.baixingAPI POST:path parameters:parameters success:successBlock failure:failureBlock];
  }
}
@end

The host app extends BXAPI to conform to PEGBaixingAPI and injects it into Pegasus:

@interface BXAPI (Pegasus) <PEGBaixingAPI>
@end

@implementation BXAPI (Pegasus)
- (void)GET:(NSString *)URLString parameters:(NSDictionary<NSString *, id> *)parameters success:(void (^)(id _Nullable))success failure:(void (^)(NSError *))failure {
  // Call existing BXAPI implementation
}
- (void)POST:(NSString *)URLString parameters:(NSDictionary<NSString *, id> *)parameters success:(void (^)(id _Nullable))success failure:(void (^)(NSError *))failure {
  // Call existing BXAPI implementation
}
@end

Finally, Pegasus is instantiated with the injected dependencies:

id<PEGBaixingAPI> baixingAPI = [BXAPI sharedInstance];
id<PEGRouter> router = [BXRouter sharedRouter];
id<PEGDataProvider> dataProvider = [BXDataProvider sharedProvider];

Pegasus *pegasus = [[Pegasus alloc] initWithConfiguration:configuration
                                                  baixingAPI:baixingAPI
                                                      router:router
                                                dataProvider:dataProvider];

Benefits include inversion of control, easier reuse across different host apps, and clear ownership of shared data.

Development and Debugging

Developing RN modules requires both native and JavaScript expertise. The team chose TypeScript for its strong type system and excellent tooling (VS Code). References such as ReactNativeTS and the Microsoft TypeScript‑React‑Native‑Starter helped bootstrap the project.

To debug TypeScript source directly, a custom RN transformer was created to preserve source‑map information, allowing breakpoints to map to the original TS files.

Shell Project

A shell app mocks host‑app dependencies, enabling RN developers to work and debug without rebuilding the full native container each time.

Crash Information

Crashlytics‑style tools (e.g., Bugly) collect native crashes, but JS stack traces are unreadable without source maps. The team built a Chrome extension to translate JS stacks back to TypeScript, emphasizing the importance of preserving source‑map files after bundling.

Code Deployment

With dependency injection solved, the RN module can be packaged as a native library and distributed via the host app’s package manager.

Regular Deployment

Standard packaging bundles JS, assets, and native code into a library consumed by the host app.

Dynamic Deployment

Dynamic deployment leverages CodePush to push updated JS bundles over the air, avoiding full app store releases. Apple’s guidelines require careful use of hot‑update mechanisms; the team currently uses CodePush in internal beta testing.

Integration example for selecting the bundle URL:

#pragma mark - RCTBridgeDelegate

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
  NSURL *bundleURL = nil;
  if (self.configuration.developmentMode) {
    bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
  } else {
    bundleURL = [CodePush bundleURLForResource:@"Pegasus" withExtension:@"js" subdirectory:nil bundle:[NSBundle pegasusBundle]];
  }
  if (!bundleURL) {
    bundleURL = [[NSBundle pegasusBundle] URLForResource:@"Pegasus" withExtension:@"js"];
  }
  return bundleURL;
}

Version compatibility is managed by ensuring that the JS bundle does not rely on native APIs that have changed; otherwise a full app update is required.

Summary

Use UIViewController/Activity containers to host RN.

Integrate RN modules as native libraries.

Leverage RN’s bridge for communication.

Apply dependency injection to resolve external dependencies.

Adopt TypeScript for a better development experience.

Employ CodePush for dynamic JS deployment.

The Pegasus solution demonstrates a practical, scalable approach to adding React Native capabilities to existing native apps.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

mobile developmentTypeScriptdependency-injectiondynamic deploymentCodePush
Baixing.com Technical Team
Written by

Baixing.com Technical Team

A collection of the Baixing.com tech team's insights and learnings, featuring one weekly technical article worth following.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.