diff --git a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js
index 77424ad5105..05c8b93c9ee 100644
--- a/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js
+++ b/packages/react-native-renderer/src/__mocks__/react-native/Libraries/ReactPrivate/ReactNativeViewConfigRegistry.js
@@ -101,10 +101,13 @@ exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> {
: '',
);
}
- viewConfigCallbacks.set(name, null);
viewConfig = callback();
processEventTypes(viewConfig);
viewConfigs.set(name, viewConfig);
+
+ // Clear the callback after the config is set so that
+ // we don't mask any errors during registration.
+ viewConfigCallbacks.set(name, null);
} else {
viewConfig = viewConfigs.get(name);
}
diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
index 6f7ddb09ec6..31b913129dd 100644
--- a/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
+++ b/packages/react-native-renderer/src/__tests__/ReactNativeEvents-test.internal.js
@@ -75,6 +75,49 @@ beforeEach(() => {
.ReactNativeViewConfigRegistry.register;
});
+it('fails to register the same event name with different types', () => {
+ const InvalidEvents = createReactNativeComponentClass('InvalidEvents', () => {
+ if (!__DEV__) {
+ // Simulate a registration error in prod.
+ throw new Error('Event cannot be both direct and bubbling: topChange');
+ }
+
+ // This view config has the same bubbling and direct event name
+ // which will fail to register in developement.
+ return {
+ uiViewClassName: 'InvalidEvents',
+ validAttributes: {
+ onChange: true,
+ },
+ bubblingEventTypes: {
+ topChange: {
+ phasedRegistrationNames: {
+ bubbled: 'onChange',
+ captured: 'onChangeCapture',
+ },
+ },
+ },
+ directEventTypes: {
+ topChange: {
+ registrationName: 'onChange',
+ },
+ },
+ };
+ });
+
+ // The first time this renders,
+ // we attempt to register the view config and fail.
+ expect(() => ReactNative.render(, 1)).toThrow(
+ 'Event cannot be both direct and bubbling: topChange',
+ );
+
+ // Continue to re-register the config and
+ // fail so that we don't mask the above failure.
+ expect(() => ReactNative.render(, 1)).toThrow(
+ 'Event cannot be both direct and bubbling: topChange',
+ );
+});
+
it('fails if unknown/unsupported event types are dispatched', () => {
expect(RCTEventEmitter.register).toHaveBeenCalledTimes(1);
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
diff --git a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js
index 5ebc0d36d39..7290c84546c 100644
--- a/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js
+++ b/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js
@@ -98,10 +98,13 @@ exports.get = function(name: string): ReactNativeBaseComponentViewConfig<> {
: '',
);
}
- viewConfigCallbacks.set(name, null);
viewConfig = callback();
processEventTypes(viewConfig);
viewConfigs.set(name, viewConfig);
+
+ // Clear the callback after the config is set so that
+ // we don't mask any errors during registration.
+ viewConfigCallbacks.set(name, null);
} else {
viewConfig = viewConfigs.get(name);
}