Skip to content
21 changes: 21 additions & 0 deletions ios/RNPurchases.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ - (void)sendEventWithName:(NSString *)name body:(id)body {

RCT_EXPORT_MODULE();

// Required for RN 0.65+ NativeEventEmitter (JavaScript class) support
//
// In JavaScript: new NativeEventEmitter(RNPurchases)
// NativeEventEmitter checks if the native module has addListener/removeListeners methods.
// Without these exported methods, construction throws in RN 0.79+.
//
// WHY export them here when our parent class RCTEventEmitter already has them?
// React Native's bridge only exposes methods that are EXPLICITLY exported with RCT_EXPORT_METHOD.
// Parent class methods are NOT automatically visible to JavaScript. We must re-export them here
// to make them callable from JS, then call [super] to use the parent's implementation.
//
// See: https://github.com/RevenueCat/react-native-purchases/issues/1298
// See: https://github.com/facebook/react-native/blob/main/packages/react-native/React/Modules/RCTEventEmitter.m#L101-L125
RCT_EXPORT_METHOD(addListener:(NSString *)eventName) {
[super addListener:eventName];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to implement similar methods for the react-native-purchases-ui SDK?

}

RCT_EXPORT_METHOD(removeListeners:(double)count) {
[super removeListeners:count];
}

RCT_EXPORT_METHOD(setupPurchases:(NSString *)apiKey
appUserID:(nullable NSString *)appUserID
purchasesAreCompletedBy:(nullable NSString *)purchasesAreCompletedBy
Expand Down
6 changes: 6 additions & 0 deletions src/purchases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ const NATIVE_MODULE_ERROR =
// Get the native module or use the browser implementation
const usingBrowserMode = shouldUseBrowserMode();
const RNPurchases = usingBrowserMode ? browserNativeModuleRNPurchases : NativeModules.RNPurchases;

// Only create event emitter if native module is available to avoid crash on import
//
// React Native 0.79+ requires native modules to implement addListener() and removeListeners()
// methods for NativeEventEmitter to work. Both iOS and Android native modules now have these.
// See: https://github.com/RevenueCat/react-native-purchases/issues/1298
// See: https://reactnative.dev/blog/2025/04/08/react-native-0.79 (Breaking Changes section)
const eventEmitter = !usingBrowserMode && RNPurchases ? new NativeEventEmitter(RNPurchases) : null;

// Helper function to check if native module is available - provides better error message than "Cannot read property X of null"
Expand Down