@samitouri / QOS-React / commits / 0347fcd007

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