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) {