Skip to content

Commit e26231e

Browse files
authored
Expose the module id through a sync TurboModule method instead of a JSI global (#4525)
## Description `GestureHandlerRootView`, `Touchable`, `NativeDetector` and `InterceptingGestureDetector` read `globalThis._RNGH_MODULE_ID` to pass the module id to their native views. The global is written by the module's JSI bindings installer, which RN runs only when the `TurboModule` is first accessed, and nothing in the library accessed it on purpose before the root view rendered. With the default Metro config it happened to work because evaluating the package index eagerly walks `legacy_createNativeWrapper` → `NativeViewGestureHandler` → `createHandler` → `RNGestureHandlerModule`, which instantiates the module. Anything that skips that chain (the deep import from the issue, a bundler that lowers default imports lazily, removing the v1 re-export) leaves the global undefined, the native view gets the codegen default `-1`, and `RNGestureHandlerRootHelper` throws "Tried to access a non-existent registry" on mount. Debug builds never show it because React Refresh enumerates every export of the index after it loads, which fires the lazy getters and instantiates the module anyway. The id is now a sync method on the module, `getModuleId`, read through a cached `getModuleId()` helper. Asking the module for the id guarantees the module is instantiated first, independently of bundle evaluation order. The JSI global, `globals.ts` and the JNI field lookup are removed. Web, Windows and the Jest mock return `-1`. The registry error message on Android now includes the module id. Also drops an unused `#include <string>` from the Android JNI module header. Fixes #4523 ## Test plan - basic-example on Android (debug) with the deep import from the issue: crashed before, renders now. - basic-example on Android (release) with the normal import and the `legacy_createNativeWrapper` re-export commented out: crashed before, renders now. - basic-example on iOS and the Empty example on web: `Touchable`, tap and pan detectors and `Pressable` respond - `yarn ts-check`, `yarn lint:js`, `yarn test`. <details> <summary>Tested on the following code:</summary> ```tsx // Repro for #4523 (same screen as apps/basic-example/src/Repro4523Content.tsx). // The root view is deep-imported so nothing but the root view itself can // instantiate the native module before it mounts. On web it is a plain View // and the module id is -1. import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { GestureDetector, Pressable, Touchable, usePanGesture, useTapGesture, } from 'react-native-gesture-handler'; import GestureHandlerRootView from 'react-native-gesture-handler/src/components/GestureHandlerRootView'; import { getModuleId } from 'react-native-gesture-handler/src/moduleId'; export default function EmptyExample() { return ( <GestureHandlerRootView style={styles.container}> <Repro4523Content /> </GestureHandlerRootView> ); } function Repro4523Content() { const [touches, setTouches] = React.useState(0); const [taps, setTaps] = React.useState(0); const [presses, setPresses] = React.useState(0); const [pan, setPan] = React.useState({ x: 0, y: 0 }); const tap = useTapGesture({ runOnJS: true, onActivate: () => setTaps((t) => t + 1), }); const panGesture = usePanGesture({ runOnJS: true, onUpdate: (e) => setPan({ x: Math.round(e.translationX), y: Math.round(e.translationY) }), }); return ( <View style={styles.content}> <Text style={styles.title}>#4523 repro</Text> <Text>module id: {getModuleId()} (increments on reload)</Text> <Touchable style={[styles.box, { backgroundColor: '#8e44ad' }]} onPress={() => setTouches((t) => t + 1)}> <Text style={styles.label}>touchable: {touches}</Text> </Touchable> <GestureDetector gesture={tap}> <View style={[styles.box, { backgroundColor: '#4a90d9' }]}> <Text style={styles.label}>tap: {taps}</Text> </View> </GestureDetector> <GestureDetector gesture={panGesture}> <View style={[styles.box, { backgroundColor: '#50b86c' }]}> <Text style={styles.label}> pan: {pan.x}, {pan.y} </Text> </View> </GestureDetector> <Pressable style={({ pressed }) => [ styles.box, { backgroundColor: pressed ? '#b03a2e' : '#e74c3c' }, ]} onPress={() => setPresses((p) => p + 1)}> <Text style={styles.label}>pressable: {presses}</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ container: { flex: 1 }, content: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 16, }, title: { fontSize: 20, fontWeight: 'bold' }, box: { width: 220, height: 80, borderRadius: 12, alignItems: 'center', justifyContent: 'center', }, label: { color: 'white', fontSize: 16 }, }); ``` </details>
1 parent 403cafa commit e26231e

18 files changed

Lines changed: 42 additions & 36 deletions

‎packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :
170170

171171
override fun getBindingsInstaller() = getBindingsInstallerCxx()
172172

173+
@ReactMethod(isBlockingSynchronousMethod = true)
174+
override fun getModuleId(): Double = moduleId.toDouble()
175+
173176
override fun invalidate() {
174177
registry.dropAllHandlers()
175178
interactionManager.reset()

‎packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootHelper.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
2727

2828
init {
2929
val registry =
30-
RNGestureHandlerModule.registries[moduleId] ?: throw Exception("Tried to access a non-existent registry")
30+
RNGestureHandlerModule.registries[moduleId]
31+
?: throw Exception("Tried to access a non-existent registry for module $moduleId")
3132

3233
UiThreadUtil.assertOnUiThread()
3334
val wrappedViewTag = wrappedView.id
@@ -65,7 +66,8 @@ class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView:
6566

6667
fun tearDown() {
6768
val registry =
68-
RNGestureHandlerModule.registries[moduleId] ?: throw Exception("Tried to access a non-existent registry")
69+
RNGestureHandlerModule.registries[moduleId]
70+
?: throw Exception("Tried to access a non-existent registry for module $moduleId")
6971

7072
Log.i(
7173
ReactConstants.TAG,

‎packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.cpp‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ RNGestureHandlerModule::getBindingsInstallerCxx() {
3737
[&, this](jsi::Runtime &runtime) {
3838
this->rnRuntime_ = &runtime;
3939
RNGHRuntimeDecorator::installRNRuntimeBindings(
40-
runtime, getModuleId(), [&](int handlerTag, int state) {
40+
runtime, [&](int handlerTag, int state) {
4141
setGestureState(handlerTag, state);
4242
});
4343
}));
@@ -73,14 +73,4 @@ void RNGestureHandlerModule::invalidateNative() {
7373
javaPart_ = nullptr;
7474
}
7575

76-
int RNGestureHandlerModule::getModuleId() {
77-
auto jthis = javaPart_;
78-
79-
auto cls = jthis->getClass();
80-
auto fieldId = cls->getField<jint>("moduleId");
81-
82-
jint value = jthis->getFieldValue(fieldId);
83-
return value;
84-
}
85-
8676
} // namespace gesturehandler

‎packages/react-native-gesture-handler/android/src/main/jni/RNGestureHandlerModule.h‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#pragma once
22
#include <ReactCommon/BindingsInstallerHolder.h>
33
#include <fbjni/fbjni.h>
4-
#include <string>
54

65
namespace gesturehandler {
76
using namespace facebook;
@@ -29,6 +28,5 @@ class RNGestureHandlerModule : public jni::HybridClass<RNGestureHandlerModule> {
2928
void setGestureState(const int handlerTag, const int state);
3029
bool installUIRuntimeBindings();
3130
void invalidateNative();
32-
int getModuleId();
3331
};
3432
} // namespace gesturehandler

‎packages/react-native-gesture-handler/apple/RNGestureHandlerModule.mm‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ - (void)installJSIBindingsWithRuntime:(jsi::Runtime &)rnRuntime
9898
_rnRuntime = &rnRuntime;
9999
__weak RNGestureHandlerModule *weakSelf = self;
100100

101-
RNGHRuntimeDecorator::installRNRuntimeBindings(rnRuntime, _moduleId, [weakSelf](int handlerTag, int state) {
101+
RNGHRuntimeDecorator::installRNRuntimeBindings(rnRuntime, [weakSelf](int handlerTag, int state) {
102102
RNGestureHandlerModule *strongSelf = weakSelf;
103103
if (strongSelf != nil) {
104104
[strongSelf setGestureState:state forHandler:handlerTag];
@@ -209,6 +209,11 @@ - (nonnull NSNumber *)installUIRuntimeBindings
209209
return _uiRuntimeDecorated ? @1 : @0;
210210
}
211211

212+
- (nonnull NSNumber *)getModuleId
213+
{
214+
return @(_moduleId);
215+
}
216+
212217
- (void)setGestureState:(int)state forHandler:(int)handlerTag
213218
{
214219
if (RCTIsMainQueue()) {

‎packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.cpp‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ using namespace facebook::react;
1818

1919
void RNGHRuntimeDecorator::installRNRuntimeBindings(
2020
jsi::Runtime &rnRuntime,
21-
int moduleId,
2221
std::function<void(int, int)> &&setGestureState) {
2322
const auto isViewFlatteningDisabled = jsi::Function::createFromHostFunction(
2423
rnRuntime,
@@ -83,10 +82,6 @@ void RNGHRuntimeDecorator::installRNRuntimeBindings(
8382

8483
rnRuntime.global().setProperty(
8584
rnRuntime, "_setGestureStateAsync", std::move(setGestureStateAsync));
86-
87-
auto moduleIdValue = jsi::Value(moduleId);
88-
rnRuntime.global().setProperty(
89-
rnRuntime, "_RNGH_MODULE_ID", std::move(moduleIdValue));
9085
}
9186

9287
void RNGHRuntimeDecorator::installUIRuntimeBindings(

‎packages/react-native-gesture-handler/shared/runtime/RNGHRuntimeDecorator.h‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class RNGHRuntimeDecorator {
1717
public:
1818
static void installRNRuntimeBindings(
1919
jsi::Runtime &rnRuntime,
20-
int moduleId,
2120
std::function<void(int, int)> &&setGestureState);
2221
static void installUIRuntimeBindings(
2322
jsi::Runtime &uiRuntime,

‎packages/react-native-gesture-handler/src/RNGestureHandlerModule.web.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,7 @@ export default {
102102
// No-op on web
103103
return true;
104104
},
105+
getModuleId() {
106+
return -1;
107+
},
105108
};

‎packages/react-native-gesture-handler/src/RNGestureHandlerModule.windows.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,7 @@ export default {
4747
flushOperations() {
4848
// NO-OP
4949
},
50+
getModuleId() {
51+
return -1;
52+
},
5053
};

‎packages/react-native-gesture-handler/src/components/GestureHandlerRootView.android.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33
import { StyleSheet } from 'react-native';
44

55
import GestureHandlerRootViewContext from '../GestureHandlerRootViewContext';
6+
import { getModuleId } from '../moduleId';
67
import type { RootViewNativeProps } from '../specs/RNGestureHandlerRootViewNativeComponent';
78
import GestureHandlerRootViewNativeComponent from '../specs/RNGestureHandlerRootViewNativeComponent';
89

@@ -18,7 +19,7 @@ export default function GestureHandlerRootView({
1819
<GestureHandlerRootViewNativeComponent
1920
style={style ?? styles.container}
2021
{...rest}
21-
moduleId={globalThis._RNGH_MODULE_ID} // Ensure moduleId is set
22+
moduleId={getModuleId()}
2223
/>
2324
</GestureHandlerRootViewContext>
2425
);

0 commit comments

Comments
 (0)