@samitouri / QOS-React-2 / commits / 195d5bb99e

Execute event handlers in the context of the instance that it's associated with (#29876)

That way we get owner stacks (native or otherwise) for `console.error` or `console.warn` inside of them. Since the `reportError` is also called within this context, we also get them for errors thrown within event listeners. You'll also be able to observe this in in the `error` event. Similar to how `onUncaughtError` is in the scope of the instance that errored - even though `onUncaughtError` doesn't kick in for event listeners. Chrome (from console.createTask): <img width="306" alt="Screenshot 2024-06-12 at 2 08 19 PM" src="https://github.com/facebook/react/assets/63648/34cd9d57-0df4-44df-a470-e89a5dd1b07d"> <img width="302" alt="Screenshot 2024-06-12 at 2 03 32 PM" src="https://github.com/facebook/react/assets/63648/678117b1-e03a-47d4-9989-8350212c8135"> Firefox (from React DevTools): <img width="493" alt="Screenshot 2024-06-12 at 2 05 01 PM" src="https://github.com/facebook/react/assets/63648/94ca224d-354a-4ec8-a886-5740bcb418e5"> (This is the parent stack since React DevTools doesn't just yet print owner stack.) (Firefox doesn't print the component stack for uncaught since we don't add component stacks for "error" events from React DevTools - just console.error. Perhaps an oversight.) If we didn't have the synthetic event system this would kind of just work natively in Chrome because we have this task active when we attach the event listeners to the DOM node and async stacks just follow along that way. In fact, if you attach a manual listener in useEffect you get this same effect. It's just because we use event delegation that this doesn't work. However, if we did get rid of the synthetic event system we'd likely still want to add a wrapper on the DOM node to set our internal current owner so that the non-native part of the system still can observe the active instance. That wouldn't work with manually attached listeners though.

Sebastian Markbåge committed Jun 12, 2024 at 16:15 UTC 195d5bb99e366889f0905779a0f9432d1624f999
2 files changed +43 -4
packages/react-dom-bindings/src/events/DOMPluginEventSystem.js
+25 -2
@@ -52,6 +52,7 @@ import {
52 enableLegacyFBSupport,
53 enableCreateEventHandleAPI,
54 enableScopeAPI,
55 + enableOwnerStacks,
56 } from 'shared/ReactFeatureFlags';
57 import {createEventListenerWrapperWithPriority} from './ReactDOMEventListener';
58 import {
@@ -70,6 +71,8 @@ import * as FormActionEventPlugin from './plugins/FormActionEventPlugin';
71
72 import reportGlobalError from 'shared/reportGlobalError';
73
74 +import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
75 +
76 type DispatchListener = {
77 instance: null | Fiber,
78 listener: Function,
@@ -255,7 +258,17 @@ function processDispatchQueueItemsInOrder(
258 if (instance !== previousInstance && event.isPropagationStopped()) {
259 return;
260 }
258 - executeDispatch(event, listener, currentTarget);
261 + if (__DEV__ && enableOwnerStacks && instance !== null) {
262 + runWithFiberInDEV(
263 + instance,
264 + executeDispatch,
265 + event,
266 + listener,
267 + currentTarget,
268 + );
269 + } else {
270 + executeDispatch(event, listener, currentTarget);
271 + }
272 previousInstance = instance;
273 }
274 } else {
@@ -264,7 +277,17 @@ function processDispatchQueueItemsInOrder(
277 if (instance !== previousInstance && event.isPropagationStopped()) {
278 return;
279 }
267 - executeDispatch(event, listener, currentTarget);
280 + if (__DEV__ && enableOwnerStacks && instance !== null) {
281 + runWithFiberInDEV(
282 + instance,
283 + executeDispatch,
284 + event,
285 + listener,
286 + currentTarget,
287 + );
288 + } else {
289 + executeDispatch(event, listener, currentTarget);
290 + }
291 previousInstance = instance;
292 }
293 }
packages/react-native-renderer/src/legacy-events/EventPluginUtils.js
+18 -2
@@ -7,6 +7,10 @@
7
8 import isArray from 'shared/isArray';
9
10 +import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
11 +
12 +import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';
13 +
14 let hasError = false;
15 let caughtError = null;
16
@@ -93,10 +97,22 @@ export function executeDispatchesInOrder(event) {
97 break;
98 }
99 // Listeners and Instances are two parallel arrays that are always in sync.
96 - executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);
100 + const listener = dispatchListeners[i];
101 + const instance = dispatchInstances[i];
102 + if (__DEV__ && enableOwnerStacks && instance !== null) {
103 + runWithFiberInDEV(instance, executeDispatch, event, listener, instance);
104 + } else {
105 + executeDispatch(event, listener, instance);
106 + }
107 }
108 } else if (dispatchListeners) {
99 - executeDispatch(event, dispatchListeners, dispatchInstances);
109 + const listener = dispatchListeners;
110 + const instance = dispatchInstances;
111 + if (__DEV__ && enableOwnerStacks && instance !== null) {
112 + runWithFiberInDEV(instance, executeDispatch, event, listener, instance);
113 + } else {
114 + executeDispatch(event, listener, instance);
115 + }
116 }
117 event._dispatchListeners = null;
118 event._dispatchInstances = null;