Add on(Caught|Uncaught|Recoverable) opts to RN (#28836)
## Overview There's currently a bug in RN now that we no longer re-throw errors. The `showErrorDialog` function in React Native only logs the errors as soft errors, and never a fatal. RN was depending on the global handler for the fatal error handling and logging. Instead of fixing this in `ReactFiberErrorDialog`, we can implement the new root options in RN to handle caught/uncaught/recoverable in the respective functions, and delete ReactFiberErrorDialog. I'll follow up with a RN PR to implement these options and fix the error handling.
Ricky committed
Apr 15, 2024 at 12:03 UTC
0347fcd0073cf529f67a05be86a0545c3efab8e2
2 files changed
+78
-6
packages/react-native-renderer/src/ReactFabric.js
+39
-3
@@ -101,11 +101,30 @@ function nativeOnCaughtError(
101
defaultOnCaughtError(error, errorInfo);
102
}
103
104
+type NativeRenderOptions = {
105
+ onUncaughtError?: (
106
+ error: mixed,
107
+ errorInfo: {+componentStack?: ?string},
108
+ ) => void,
109
+ onCaughtError?: (
110
+ error: mixed,
111
+ errorInfo: {
112
+ +componentStack?: ?string,
113
+ +errorBoundary?: ?React$Component<any, any>,
114
+ },
115
+ ) => void,
116
+ onRecoverableError?: (
117
+ error: mixed,
118
+ errorInfo: {+componentStack?: ?string},
119
+ ) => void,
120
+};
121
+
122
function render(
123
element: Element<ElementType>,
124
containerTag: number,
125
callback: ?() => void,
126
concurrentRoot: ?boolean,
127
+ options?: NativeRenderOptions,
128
): ?ElementRef<ElementType> {
129
if (disableLegacyMode && !concurrentRoot) {
130
throw new Error('render: Unsupported Legacy Mode API.');
@@ -114,6 +133,23 @@ function render(
133
let root = roots.get(containerTag);
134
135
if (!root) {
136
+ // TODO: these defaults are for backwards compatibility.
137
+ // Once RN implements these options internally,
138
+ // we can remove the defaults and ReactFiberErrorDialog.
139
+ let onUncaughtError = nativeOnUncaughtError;
140
+ let onCaughtError = nativeOnCaughtError;
141
+ let onRecoverableError = defaultOnRecoverableError;
142
+
143
+ if (options && options.onUncaughtError !== undefined) {
144
+ onUncaughtError = options.onUncaughtError;
145
+ }
146
+ if (options && options.onCaughtError !== undefined) {
147
+ onCaughtError = options.onCaughtError;
148
+ }
149
+ if (options && options.onRecoverableError !== undefined) {
150
+ onRecoverableError = options.onRecoverableError;
151
+ }
152
+
153
// TODO (bvaughn): If we decide to keep the wrapper component,
154
// We could create a wrapper for containerTag as well to reduce special casing.
155
root = createContainer(
@@ -123,9 +159,9 @@ function render(
159
false,
160
null,
161
'',
126
- nativeOnUncaughtError,
127
- nativeOnCaughtError,
128
- defaultOnRecoverableError,
162
+ onUncaughtError,
163
+ onCaughtError,
164
+ onRecoverableError,
165
null,
166
);
167
roots.set(containerTag, root);
packages/react-native-renderer/src/ReactNativeRenderer.js
+39
-3
@@ -106,10 +106,29 @@ function nativeOnCaughtError(
106
defaultOnCaughtError(error, errorInfo);
107
}
108
109
+type NativeRenderOptions = {
110
+ onUncaughtError?: (
111
+ error: mixed,
112
+ errorInfo: {+componentStack?: ?string},
113
+ ) => void,
114
+ onCaughtError?: (
115
+ error: mixed,
116
+ errorInfo: {
117
+ +componentStack?: ?string,
118
+ +errorBoundary?: ?React$Component<any, any>,
119
+ },
120
+ ) => void,
121
+ onRecoverableError?: (
122
+ error: mixed,
123
+ errorInfo: {+componentStack?: ?string},
124
+ ) => void,
125
+};
126
+
127
function render(
128
element: Element<ElementType>,
129
containerTag: number,
130
callback: ?() => void,
131
+ options?: NativeRenderOptions,
132
): ?ElementRef<ElementType> {
133
if (disableLegacyMode) {
134
throw new Error('render: Unsupported Legacy Mode API.');
@@ -118,6 +137,23 @@ function render(
137
let root = roots.get(containerTag);
138
139
if (!root) {
140
+ // TODO: these defaults are for backwards compatibility.
141
+ // Once RN implements these options internally,
142
+ // we can remove the defaults and ReactFiberErrorDialog.
143
+ let onUncaughtError = nativeOnUncaughtError;
144
+ let onCaughtError = nativeOnCaughtError;
145
+ let onRecoverableError = defaultOnRecoverableError;
146
+
147
+ if (options && options.onUncaughtError !== undefined) {
148
+ onUncaughtError = options.onUncaughtError;
149
+ }
150
+ if (options && options.onCaughtError !== undefined) {
151
+ onCaughtError = options.onCaughtError;
152
+ }
153
+ if (options && options.onRecoverableError !== undefined) {
154
+ onRecoverableError = options.onRecoverableError;
155
+ }
156
+
157
// TODO (bvaughn): If we decide to keep the wrapper component,
158
// We could create a wrapper for containerTag as well to reduce special casing.
159
root = createContainer(
@@ -127,9 +163,9 @@ function render(
163
false,
164
null,
165
'',
130
- nativeOnUncaughtError,
131
- nativeOnCaughtError,
132
- defaultOnRecoverableError,
166
+ onUncaughtError,
167
+ onCaughtError,
168
+ onRecoverableError,
169
null,
170
);
171
roots.set(containerTag, root);