Skip to main content

Module events (old arch)

info

You may notice, that JS specs contain codegen-related methods, classes, types, etc. to make things more future-proof.

That's because:

  • those elements are available since RN older versions (even from v0.65)
  • those elements are falling back to "old architecture" implementation (e.g. codegenNativeComponent)
  • it introduces type safety for exposed native parts on JS side
  • it's much easier to keep single specification on JS side - when old arch will be dropped, there'll be no need to change anything on JS side

So to make it easier, let's use them, to get you more familiar with it 👍

Event emitter declaration

To make it possible for the module to emit events, JS spec needs to declare 2 methods:

src/NativeMyAwesomeModule.ts
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

// ...

export interface Spec extends TurboModule {
addListener(eventName: string): void
removeListeners(count: number): void
}

// TurboModuleRegistry uses NativeModules['MyAwesomeModule'] on old arch
export default TurboModuleRegistry.getEnforcing<Spec>('MyAwesomeModule');

Event emitter implementation in native code

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

@interface NativeMyAwesomeModule : RCTEventEmitter<RCTBridgeModule>

@end
ios/NativeMyAwesomeModule.mm
#import "NativeMyAwesomeModule.h"

@implementation NativeMyAwesomeModule {
BOOL hasListeners;
}

RCT_EXPORT_MODULE(NativeMyAwesomeModule)

- (NSArray<NSString *> *)supportedEvents
{
return @[@"decentEvent", @"notGreatEvent"];
}

- (void)startObserving
{
hasListeners = YES;
}

- (void)stopObserving
{
hasListeners = NO;
}

- (void)onSomeNativeEvent:(NSString *)someNativeValue
{
if (hasListeners) {
[self sendEventWithName:@"decentEvent" body:@{ @"value": someNativeValue }];
}
}

@end

To enhance iOS native module with event emitting feature, it has to extend RCTEventEmitter abstract class.

Then in the implementation part, module has to override supportedEvents getter which declares names for events emitted by this module. It has to also override startObserving and stopObserving methods, which are called when first and last listeners are added/removed.

To emit event, the module should call sendEventWithName:body: method, which is inherited from RCTEventEmitter class. It takes event name and an object payload (NSDictionary type), which will be sent to JS listener.

Event emitter implementation in JS code

src/MyAwesomeHook.ts
import { NativeEventEmitter, Platform } from 'react-native';

import { MyAwesomeModule } from 'my-awesome-module';

const moduleEventEmitter = new NativeEventEmitter(Platform.OS === 'ios' ? MyAwesomeModule : undefined);

// ...

useEffect(() => {
const subscription = moduleEventEmitter.addListener('decentEvent', ({ value }: { value: string }) => {
console.log({ someNativeValue: value });
});

return () => {
subscription.remove();
};
}, []);

To start listening to events emitted from native side, you have to:

  • create NativeEventEmitter instance
  • invoke addListener method on that instance, providing the event name and the listener function

When the event is emitted, all listeners for that specific event will be called with the payload value.

When the listener is not needed, it should be removed - as a cleanup, it's recommended to invoke remove method on the object returned from addListener method call. It will unregister provided listener from emitted native events.