diff --git a/packages/react-dom/src/client/ReactDOMEventHandle.js b/packages/react-dom/src/client/ReactDOMEventHandle.js
index 19e69126aef0c..9e9219a1bdc16 100644
--- a/packages/react-dom/src/client/ReactDOMEventHandle.js
+++ b/packages/react-dom/src/client/ReactDOMEventHandle.js
@@ -15,6 +15,7 @@ import type {
} from '../shared/ReactDOMTypes';
import {getEventPriorityForListenerSystem} from '../events/DOMEventProperties';
+import {allNativeEvents} from '../events/EventRegistry';
import {
getClosestInstanceFromNode,
getEventHandlerListeners,
@@ -33,6 +34,7 @@ import {IS_EVENT_HANDLE_NON_MANAGED_NODE} from '../events/EventSystemFlags';
import {
enableScopeAPI,
enableCreateEventHandleAPI,
+ enableEagerRootListeners,
} from 'shared/ReactFeatureFlags';
import invariant from 'shared/invariant';
@@ -178,6 +180,26 @@ export function createEventHandle(
): ReactDOMEventHandle {
if (enableCreateEventHandleAPI) {
const domEventName = ((type: any): DOMEventName);
+
+ if (enableEagerRootListeners) {
+ // We cannot support arbitrary native events with eager root listeners
+ // because the eager strategy relies on knowing the whole list ahead of time.
+ // If we wanted to support this, we'd have to add code to keep track
+ // (or search) for all portal and root containers, and lazily add listeners
+ // to them whenever we see a previously unknown event. This seems like a lot
+ // of complexity for something we don't even have a particular use case for.
+ // Unfortunately, the downside of this invariant is that *removing* a native
+ // event from the list of known events has now become a breaking change for
+ // any code relying on the createEventHandle API.
+ invariant(
+ allNativeEvents.has(domEventName) ||
+ domEventName === 'beforeblur' ||
+ domEventName === 'afterblur',
+ 'Cannot call unstable_createEventHandle with "%s", as it is not an event known to React.',
+ domEventName,
+ );
+ }
+
let isCapturePhaseListener = false;
let isPassiveListener = undefined; // Undefined means to use the browser default
let listenerPriority;
diff --git a/packages/react-dom/src/events/DOMEventProperties.js b/packages/react-dom/src/events/DOMEventProperties.js
index 342e653bd48a3..8c5090ecaee4b 100644
--- a/packages/react-dom/src/events/DOMEventProperties.js
+++ b/packages/react-dom/src/events/DOMEventProperties.js
@@ -201,8 +201,9 @@ export function getEventPriorityForListenerSystem(
}
if (__DEV__) {
console.warn(
- 'The event "type" provided to createEventHandle() does not have a known priority type.' +
+ 'The event "%s" provided to createEventHandle() does not have a known priority type.' +
' It is recommended to provide a "priority" option to specify a priority.',
+ type,
);
}
return ContinuousEvent;
diff --git a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
index d0e5ba484573d..17d624d8e1f05 100644
--- a/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
+++ b/packages/react-dom/src/events/__tests__/DOMPluginEventSystem-test.internal.js
@@ -2401,85 +2401,97 @@ describe('DOMPluginEventSystem', () => {
);
let setCustomEventHandle;
+ if (gate(flags => flags.enableEagerRootListeners)) {
+ // With eager listeners, supporting custom events via this API doesn't make sense
+ // because we can't know a full list of them ahead of time. Let's check we throw
+ // since otherwise we'd end up with inconsistent behavior, like no portal bubbling.
+ expect(() => {
+ setCustomEventHandle = ReactDOM.unstable_createEventHandle(
+ 'custom-event',
+ );
+ }).toThrow(
+ 'Cannot call unstable_createEventHandle with "custom-event", as it is not an event known to React.',
+ );
+ } else {
+ // Test that we get a warning when we don't provide an explicit priority
+ expect(() => {
+ setCustomEventHandle = ReactDOM.unstable_createEventHandle(
+ 'custom-event',
+ );
+ }).toWarnDev(
+ 'Warning: The event "custom-event" provided to createEventHandle() does not have a known priority type. ' +
+ 'It is recommended to provide a "priority" option to specify a priority.',
+ {withoutStack: true},
+ );
- // Test that we get a warning when we don't provide an explicit priority
- expect(() => {
setCustomEventHandle = ReactDOM.unstable_createEventHandle(
'custom-event',
+ {
+ priority: 0, // Discrete
+ },
);
- }).toWarnDev(
- 'Warning: The event "type" provided to createEventHandle() does not have a known priority type. ' +
- 'It is recommended to provide a "priority" option to specify a priority.',
- {withoutStack: true},
- );
- setCustomEventHandle = ReactDOM.unstable_createEventHandle(
- 'custom-event',
- {
- priority: 0, // Discrete
- },
- );
-
- const setCustomCaptureHandle = ReactDOM.unstable_createEventHandle(
- 'custom-event',
- {
- capture: true,
- priority: 0, // Discrete
- },
- );
+ const setCustomCaptureHandle = ReactDOM.unstable_createEventHandle(
+ 'custom-event',
+ {
+ capture: true,
+ priority: 0, // Discrete
+ },
+ );
- function Test() {
- React.useEffect(() => {
- const clearCustom1 = setCustomEventHandle(
- buttonRef.current,
- onCustomEvent,
- );
- const clearCustom2 = setCustomCaptureHandle(
- buttonRef.current,
- onCustomEventCapture,
- );
- const clearCustom3 = setCustomEventHandle(
- divRef.current,
- onCustomEvent,
- );
- const clearCustom4 = setCustomCaptureHandle(
- divRef.current,
- onCustomEventCapture,
- );
+ const Test = () => {
+ React.useEffect(() => {
+ const clearCustom1 = setCustomEventHandle(
+ buttonRef.current,
+ onCustomEvent,
+ );
+ const clearCustom2 = setCustomCaptureHandle(
+ buttonRef.current,
+ onCustomEventCapture,
+ );
+ const clearCustom3 = setCustomEventHandle(
+ divRef.current,
+ onCustomEvent,
+ );
+ const clearCustom4 = setCustomCaptureHandle(
+ divRef.current,
+ onCustomEventCapture,
+ );
- return () => {
- clearCustom1();
- clearCustom2();
- clearCustom3();
- clearCustom4();
- };
- });
+ return () => {
+ clearCustom1();
+ clearCustom2();
+ clearCustom3();
+ clearCustom4();
+ };
+ });
- return (
-
- );
- }
+ return (
+
+ );
+ };
- ReactDOM.render(