@samitouri / QOS-React-2 / commits / efd8f6442d

Resolve default onRecoverableError at root init (#23264)

Minor follow up to initial onRecoverableError PR. When onRecoverableError is not provided to `createRoot`, the renderer falls back to a default implementation. Originally I implemented this with a host config method, but what we can do instead is pass the default implementation the root constructor as if it were a user provided one.

Andrew Clark committed Feb 10, 2022 at 07:59 UTC efd8f6442d1aa7c4566fe812cba03e7e83aaccc3
16 files changed +57 -55
packages/react-art/src/ReactARTHostConfig.js
-4
@@ -451,7 +451,3 @@ export function preparePortalMount(portalInstance: any): void {
451 export function detachDeletedInstance(node: Instance): void {
452 // noop
453 }
454 -
455 -export function logRecoverableError(error) {
456 - // noop
457 -}
packages/react-dom/src/client/ReactDOMHostConfig.js
-12
@@ -374,18 +374,6 @@ export function getCurrentEventPriority(): * {
374 return getEventPriority(currentEvent.type);
375 }
376
377 -/* global reportError */
378 -export const logRecoverableError =
379 - typeof reportError === 'function'
380 - ? // In modern browsers, reportError will dispatch an error event,
381 - // emulating an uncaught JavaScript error.
382 - reportError
383 - : (error: mixed) => {
384 - // In older browsers and test environments, fallback to console.error.
385 - // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
386 - console.error(error);
387 - };
388 -
377 export const isPrimaryRenderer = true;
378 export const warnsIfNotActing = true;
379 // This initialization code may run even on server environments
packages/react-dom/src/client/ReactDOMLegacy.js
+6 -1
@@ -102,6 +102,11 @@ function getReactRootElementInContainer(container: any) {
102 }
103 }
104
105 +function noopOnRecoverableError() {
106 + // This isn't reachable because onRecoverableError isn't called in the
107 + // legacy API.
108 +}
109 +
110 function legacyCreateRootFromDOMContainer(
111 container: Container,
112 forceHydrate: boolean,
@@ -122,7 +127,7 @@ function legacyCreateRootFromDOMContainer(
127 false, // isStrictMode
128 false, // concurrentUpdatesByDefaultOverride,
129 '', // identifierPrefix
125 - null,
130 + noopOnRecoverableError,
131 );
132 markContainerAsRoot(root.current, container);
133
packages/react-dom/src/client/ReactDOMRoot.js
+14 -2
@@ -65,6 +65,18 @@ import {
65 import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
66 import {allowConcurrentByDefault} from 'shared/ReactFeatureFlags';
67
68 +/* global reportError */
69 +const defaultOnRecoverableError =
70 + typeof reportError === 'function'
71 + ? // In modern browsers, reportError will dispatch an error event,
72 + // emulating an uncaught JavaScript error.
73 + reportError
74 + : (error: mixed) => {
75 + // In older browsers and test environments, fallback to console.error.
76 + // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
77 + console.error(error);
78 + };
79 +
80 function ReactDOMRoot(internalRoot: FiberRoot) {
81 this._internalRoot = internalRoot;
82 }
@@ -145,7 +157,7 @@ export function createRoot(
157 let isStrictMode = false;
158 let concurrentUpdatesByDefaultOverride = false;
159 let identifierPrefix = '';
148 - let onRecoverableError = null;
160 + let onRecoverableError = defaultOnRecoverableError;
161 if (options !== null && options !== undefined) {
162 if (__DEV__) {
163 if ((options: any).hydrate) {
@@ -220,7 +232,7 @@ export function hydrateRoot(
232 let isStrictMode = false;
233 let concurrentUpdatesByDefaultOverride = false;
234 let identifierPrefix = '';
223 - let onRecoverableError = null;
235 + let onRecoverableError = defaultOnRecoverableError;
236 if (options !== null && options !== undefined) {
237 if (options.unstable_strictMode === true) {
238 isStrictMode = true;
packages/react-native-renderer/src/ReactFabric.js
+7 -1
@@ -195,6 +195,12 @@ function sendAccessibilityEvent(handle: any, eventType: string) {
195 }
196 }
197
198 +function onRecoverableError(error) {
199 + // TODO: Expose onRecoverableError option to userspace
200 + // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
201 + console.error(error);
202 +}
203 +
204 function render(
205 element: Element<ElementType>,
206 containerTag: number,
@@ -214,7 +220,7 @@ function render(
220 false,
221 null,
222 '',
217 - null,
223 + onRecoverableError,
224 );
225 roots.set(containerTag, root);
226 }
packages/react-native-renderer/src/ReactFabricHostConfig.js
-4
@@ -525,7 +525,3 @@ export function preparePortalMount(portalInstance: Instance): void {
525 export function detachDeletedInstance(node: Instance): void {
526 // noop
527 }
528 -
529 -export function logRecoverableError(error: mixed): void {
530 - // noop
531 -}
packages/react-native-renderer/src/ReactNativeHostConfig.js
-4
@@ -513,7 +513,3 @@ export function preparePortalMount(portalInstance: Instance): void {
513 export function detachDeletedInstance(node: Instance): void {
514 // noop
515 }
516 -
517 -export function logRecoverableError(error: mixed): void {
518 - // noop
519 -}
packages/react-native-renderer/src/ReactNativeRenderer.js
+7 -1
@@ -192,6 +192,12 @@ function sendAccessibilityEvent(handle: any, eventType: string) {
192 }
193 }
194
195 +function onRecoverableError(error) {
196 + // TODO: Expose onRecoverableError option to userspace
197 + // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
198 + console.error(error);
199 +}
200 +
201 function render(
202 element: Element<ElementType>,
203 containerTag: number,
@@ -210,7 +216,7 @@ function render(
216 false,
217 null,
218 '',
213 - null,
219 + onRecoverableError,
220 );
221 roots.set(containerTag, root);
222 }
packages/react-noop-renderer/src/createReactNoop.js
+9 -3
@@ -938,6 +938,12 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
938 return NoopRenderer.flushSync(fn);
939 }
940
941 + function onRecoverableError(error) {
942 + // TODO: Turn this on once tests are fixed
943 + // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
944 + // console.error(error);
945 + }
946 +
947 let idCounter = 0;
948
949 const ReactNoop = {
@@ -966,7 +972,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
972 null,
973 false,
974 '',
969 - null,
975 + onRecoverableError,
976 );
977 roots.set(rootID, root);
978 }
@@ -988,7 +994,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
994 null,
995 false,
996 '',
991 - null,
997 + onRecoverableError,
998 );
999 return {
1000 _Scheduler: Scheduler,
@@ -1018,7 +1024,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
1024 null,
1025 false,
1026 '',
1021 - null,
1027 + onRecoverableError,
1028 );
1029 return {
1030 _Scheduler: Scheduler,
packages/react-reconciler/src/ReactFiberReconciler.new.js
+1 -1
@@ -245,7 +245,7 @@ export function createContainer(
245 isStrictMode: boolean,
246 concurrentUpdatesByDefaultOverride: null | boolean,
247 identifierPrefix: string,
248 - onRecoverableError: null | ((error: mixed) => void),
248 + onRecoverableError: (error: mixed) => void,
249 ): OpaqueRoot {
250 return createFiberRoot(
251 containerInfo,
packages/react-reconciler/src/ReactFiberReconciler.old.js
+1 -1
@@ -245,7 +245,7 @@ export function createContainer(
245 isStrictMode: boolean,
246 concurrentUpdatesByDefaultOverride: null | boolean,
247 identifierPrefix: string,
248 - onRecoverableError: null | ((error: mixed) => void),
248 + onRecoverableError: (error: mixed) => void,
249 ): OpaqueRoot {
250 return createFiberRoot(
251 containerInfo,
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+2 -9
@@ -77,7 +77,6 @@ import {
77 supportsMicrotasks,
78 errorHydratingContainer,
79 scheduleMicrotask,
80 - logRecoverableError,
80 } from './ReactFiberHostConfig';
81
82 import {
@@ -2113,16 +2112,10 @@ function commitRootImpl(
2112 if (recoverableErrors !== null) {
2113 // There were errors during this render, but recovered from them without
2114 // needing to surface it to the UI. We log them here.
2115 + const onRecoverableError = root.onRecoverableError;
2116 for (let i = 0; i < recoverableErrors.length; i++) {
2117 const recoverableError = recoverableErrors[i];
2118 - const onRecoverableError = root.onRecoverableError;
2119 - if (onRecoverableError !== null) {
2120 - onRecoverableError(recoverableError);
2121 - } else {
2122 - // No user-provided onRecoverableError. Use the default behavior
2123 - // provided by the renderer's host config.
2124 - logRecoverableError(recoverableError);
2125 - }
2118 + onRecoverableError(recoverableError);
2119 }
2120 }
2121
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+2 -9
@@ -77,7 +77,6 @@ import {
77 supportsMicrotasks,
78 errorHydratingContainer,
79 scheduleMicrotask,
80 - logRecoverableError,
80 } from './ReactFiberHostConfig';
81
82 import {
@@ -2113,16 +2112,10 @@ function commitRootImpl(
2112 if (recoverableErrors !== null) {
2113 // There were errors during this render, but recovered from them without
2114 // needing to surface it to the UI. We log them here.
2115 + const onRecoverableError = root.onRecoverableError;
2116 for (let i = 0; i < recoverableErrors.length; i++) {
2117 const recoverableError = recoverableErrors[i];
2118 - const onRecoverableError = root.onRecoverableError;
2119 - if (onRecoverableError !== null) {
2120 - onRecoverableError(recoverableError);
2121 - } else {
2122 - // No user-provided onRecoverableError. Use the default behavior
2123 - // provided by the renderer's host config.
2124 - logRecoverableError(recoverableError);
2125 - }
2118 + onRecoverableError(recoverableError);
2119 }
2120 }
2121
packages/react-reconciler/src/ReactInternalTypes.js
+1 -1
@@ -247,7 +247,7 @@ type BaseFiberRootProperties = {|
247 // a reference to.
248 identifierPrefix: string,
249
250 - onRecoverableError: null | ((error: mixed) => void),
250 + onRecoverableError: (error: mixed) => void,
251 |};
252
253 // The following attributes are only used by DevTools and are only present in DEV builds.
packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js
-1
@@ -68,7 +68,6 @@ export const prepareScopeUpdate = $$$hostConfig.preparePortalMount;
68 export const getInstanceFromScope = $$$hostConfig.getInstanceFromScope;
69 export const getCurrentEventPriority = $$$hostConfig.getCurrentEventPriority;
70 export const detachDeletedInstance = $$$hostConfig.detachDeletedInstance;
71 -export const logRecoverableError = $$$hostConfig.logRecoverableError;
71
72 // -------------------
73 // Microtasks
packages/react-test-renderer/src/ReactTestRenderer.js
+7 -1
@@ -437,6 +437,12 @@ function propsMatch(props: Object, filter: Object): boolean {
437 return true;
438 }
439
440 +function onRecoverableError(error) {
441 + // TODO: Expose onRecoverableError option to userspace
442 + // eslint-disable-next-line react-internal/no-production-logging, react-internal/warning-args
443 + console.error(error);
444 +}
445 +
446 function create(element: React$Element<any>, options: TestRendererOptions) {
447 let createNodeMock = defaultTestOptions.createNodeMock;
448 let isConcurrent = false;
@@ -472,7 +478,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
478 isStrictMode,
479 concurrentUpdatesByDefault,
480 '',
475 - null,
481 + onRecoverableError,
482 );
483
484 if (root == null) {