Skip to content

Commit

Permalink
Add on(Caught|Uncaught|Recoverable) opts to RN (#28836)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
rickhanlonii authored Apr 15, 2024
1 parent c113503 commit 0347fcd
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 6 deletions.
42 changes: 39 additions & 3 deletions packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,30 @@ function nativeOnCaughtError(
defaultOnCaughtError(error, errorInfo);
}

type NativeRenderOptions = {
onUncaughtError?: (
error: mixed,
errorInfo: {+componentStack?: ?string},
) => void,
onCaughtError?: (
error: mixed,
errorInfo: {
+componentStack?: ?string,
+errorBoundary?: ?React$Component<any, any>,
},
) => void,
onRecoverableError?: (
error: mixed,
errorInfo: {+componentStack?: ?string},
) => void,
};

function render(
element: Element<ElementType>,
containerTag: number,
callback: ?() => void,
concurrentRoot: ?boolean,
options?: NativeRenderOptions,
): ?ElementRef<ElementType> {
if (disableLegacyMode && !concurrentRoot) {
throw new Error('render: Unsupported Legacy Mode API.');
Expand All @@ -114,6 +133,23 @@ function render(
let root = roots.get(containerTag);

if (!root) {
// TODO: these defaults are for backwards compatibility.
// Once RN implements these options internally,
// we can remove the defaults and ReactFiberErrorDialog.
let onUncaughtError = nativeOnUncaughtError;
let onCaughtError = nativeOnCaughtError;
let onRecoverableError = defaultOnRecoverableError;

if (options && options.onUncaughtError !== undefined) {
onUncaughtError = options.onUncaughtError;
}
if (options && options.onCaughtError !== undefined) {
onCaughtError = options.onCaughtError;
}
if (options && options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}

// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(
Expand All @@ -123,9 +159,9 @@ function render(
false,
null,
'',
nativeOnUncaughtError,
nativeOnCaughtError,
defaultOnRecoverableError,
onUncaughtError,
onCaughtError,
onRecoverableError,
null,
);
roots.set(containerTag, root);
Expand Down
42 changes: 39 additions & 3 deletions packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,29 @@ function nativeOnCaughtError(
defaultOnCaughtError(error, errorInfo);
}

type NativeRenderOptions = {
onUncaughtError?: (
error: mixed,
errorInfo: {+componentStack?: ?string},
) => void,
onCaughtError?: (
error: mixed,
errorInfo: {
+componentStack?: ?string,
+errorBoundary?: ?React$Component<any, any>,
},
) => void,
onRecoverableError?: (
error: mixed,
errorInfo: {+componentStack?: ?string},
) => void,
};

function render(
element: Element<ElementType>,
containerTag: number,
callback: ?() => void,
options?: NativeRenderOptions,
): ?ElementRef<ElementType> {
if (disableLegacyMode) {
throw new Error('render: Unsupported Legacy Mode API.');
Expand All @@ -118,6 +137,23 @@ function render(
let root = roots.get(containerTag);

if (!root) {
// TODO: these defaults are for backwards compatibility.
// Once RN implements these options internally,
// we can remove the defaults and ReactFiberErrorDialog.
let onUncaughtError = nativeOnUncaughtError;
let onCaughtError = nativeOnCaughtError;
let onRecoverableError = defaultOnRecoverableError;

if (options && options.onUncaughtError !== undefined) {
onUncaughtError = options.onUncaughtError;
}
if (options && options.onCaughtError !== undefined) {
onCaughtError = options.onCaughtError;
}
if (options && options.onRecoverableError !== undefined) {
onRecoverableError = options.onRecoverableError;
}

// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(
Expand All @@ -127,9 +163,9 @@ function render(
false,
null,
'',
nativeOnUncaughtError,
nativeOnCaughtError,
defaultOnRecoverableError,
onUncaughtError,
onCaughtError,
onRecoverableError,
null,
);
roots.set(containerTag, root);
Expand Down

0 comments on commit 0347fcd

Please sign in to comment.