Files
react-native/packages/react-native-codegen
Christoph Purrer 4bf5575490 Make codegen'd TurboModule event emitters no-op when the emitter callback is absent (#57893)
Summary:
Pull Request resolved: https://github.com/react/react-native/pull/57893

The codegen'd TurboModule `emitOn<Event>` methods invoked their `EventEmitterCallback` without checking it was set. That callback is installed by the generated `*SpecJSI` constructor, which runs when JS first looks the module up — so a native module that emits before that point invoked an empty `std::function` on iOS (`std::bad_function_call`) or a null field on Android (NPE). Native code commonly holds the module instance and pushes events well before any JS surface mounts, so call sites had to wrap every emit in a try/catch to stay crash-free.

Both generators now read the callback into a local and no-op when it is absent:

- ObjC++ (`serializeEventEmitter.js`): copies `_eventEmitterCallback`, calls it only if non-empty.
- Java (`GenerateModuleJavaSpec.js`): copies the `Nullable CxxCallbackImpl` field and null-checks it.

The local is for readability, not synchronization: the callback is installed once and never cleared, and Java reference reads are already atomic.

The `setEventEmitterCallback` lambdas had a separate lifetime bug: they captured `eventEmitterMap_` by reference and looked events up with `operator[]`. The Java/ObjC module owns the callback and can outlive the C++ `*SpecJSI` that installed it, so a stale callback dereferenced a dangling map; and `operator[]` silently default-inserted a null `shared_ptr` for an unknown event name, which the next line dereferenced. They now capture a copy of the map and use a checked `find` (`serializeModule.js`, `JavaTurboModule.cpp`). Every emitter is registered before the callback is installed, so the copy is complete.

Note the scope of the guarantee: it covers the ObjC++ and Java generators, which route through an `EventEmitterCallback`. A C++-only TurboModule (`<Module>CxxSpec`, from `GenerateModuleH.js`) has no callback to check — it emits through `eventEmitterMap_` entries its own constructor registers — so the "emit unconditionally" guidance is about the callback, not about emitting before construction finishes.

Changelog:
[General][Fixed] - TurboModule event emitters no longer throw when an event is emitted before the emitter callback is installed

Reviewed By: javache

Differential Revision: D115130767

fbshipit-source-id: 7d7b9a706b8983968e3e1207b22c66322f04a223
2026-08-17 10:41:02 -07:00
..

@react-native/codegen

npm npm downloads

Code generation tools for React Native. Parses TypeScript and Flow NativeModule and Component specs and generates the native binding code used by the New Architecture.