mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
a053716077
Stacked on #28627. This makes error logging configurable using these `createRoot`/`hydrateRoot` options: ``` onUncaughtError(error: mixed, errorInfo: {componentStack?: ?string}) => void onCaughtError(error: mixed, errorInfo: {componentStack?: ?string, errorBoundary?: ?React.Component<any, any>}) => void onRecoverableError(error: mixed, errorInfo: {digest?: ?string, componentStack?: ?string}) => void ``` We already have the `onRecoverableError` option since before. Overriding these can be used to implement custom error dialogs (with access to the `componentStack`). It can also be used to silence caught errors when testing an error boundary or if you prefer not getting logs for caught errors that you've already handled in an error boundary. I currently expose the error boundary instance but I think we should probably remove that since it doesn't make sense for non-class error boundaries and isn't very useful anyway. It's also unclear what it should do when an error is rethrown from one boundary to another. Since these are public APIs now we can implement the ReactFiberErrorDialog forks using these options at the roots of the builds. So I unforked those files and instead passed a custom option for the native and www builds. To do this I had to fork the ReactDOMLegacy file into ReactDOMRootFB which is a duplication but that will go away as soon as the FB fork is the only legacy root.
211 lines
5.9 KiB
JavaScript
211 lines
5.9 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactPortal, ReactNodeList} from 'shared/ReactTypes';
|
|
import type {ElementRef, Element, ElementType} from 'react';
|
|
import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
|
|
|
|
import './ReactNativeInjection';
|
|
|
|
import {
|
|
batchedUpdates as batchedUpdatesImpl,
|
|
discreteUpdates,
|
|
createContainer,
|
|
updateContainer,
|
|
injectIntoDevTools,
|
|
getPublicRootInstance,
|
|
defaultOnUncaughtError,
|
|
defaultOnCaughtError,
|
|
defaultOnRecoverableError,
|
|
} from 'react-reconciler/src/ReactFiberReconciler';
|
|
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
|
import {getStackByFiberInDevAndProd} from 'react-reconciler/src/ReactFiberComponentStack';
|
|
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
|
|
import {
|
|
setBatchingImplementation,
|
|
batchedUpdates,
|
|
} from './legacy-events/ReactGenericBatching';
|
|
import ReactVersion from 'shared/ReactVersion';
|
|
// Modules provided by RN:
|
|
import {UIManager} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
|
|
|
import {getClosestInstanceFromNode} from './ReactNativeComponentTree';
|
|
import {
|
|
getInspectorDataForViewTag,
|
|
getInspectorDataForViewAtPoint,
|
|
getInspectorDataForInstance,
|
|
} from './ReactNativeFiberInspector';
|
|
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
|
|
import {
|
|
findHostInstance_DEPRECATED,
|
|
findNodeHandle,
|
|
dispatchCommand,
|
|
sendAccessibilityEvent,
|
|
isChildPublicInstance,
|
|
} from './ReactNativePublicCompat';
|
|
|
|
// Module provided by RN:
|
|
import {ReactFiberErrorDialog} from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
|
|
|
|
if (typeof ReactFiberErrorDialog.showErrorDialog !== 'function') {
|
|
throw new Error(
|
|
'Expected ReactFiberErrorDialog.showErrorDialog to be a function.',
|
|
);
|
|
}
|
|
|
|
function nativeOnUncaughtError(
|
|
error: mixed,
|
|
errorInfo: {+componentStack?: ?string},
|
|
): void {
|
|
const componentStack =
|
|
errorInfo.componentStack != null ? errorInfo.componentStack : '';
|
|
const logError = ReactFiberErrorDialog.showErrorDialog({
|
|
errorBoundary: null,
|
|
error,
|
|
componentStack,
|
|
});
|
|
|
|
// Allow injected showErrorDialog() to prevent default console.error logging.
|
|
// This enables renderers like ReactNative to better manage redbox behavior.
|
|
if (logError === false) {
|
|
return;
|
|
}
|
|
|
|
defaultOnUncaughtError(error, errorInfo);
|
|
}
|
|
function nativeOnCaughtError(
|
|
error: mixed,
|
|
errorInfo: {
|
|
+componentStack?: ?string,
|
|
+errorBoundary?: ?React$Component<any, any>,
|
|
},
|
|
): void {
|
|
const errorBoundary = errorInfo.errorBoundary;
|
|
const componentStack =
|
|
errorInfo.componentStack != null ? errorInfo.componentStack : '';
|
|
const logError = ReactFiberErrorDialog.showErrorDialog({
|
|
errorBoundary,
|
|
error,
|
|
componentStack,
|
|
});
|
|
|
|
// Allow injected showErrorDialog() to prevent default console.error logging.
|
|
// This enables renderers like ReactNative to better manage redbox behavior.
|
|
if (logError === false) {
|
|
return;
|
|
}
|
|
|
|
defaultOnCaughtError(error, errorInfo);
|
|
}
|
|
|
|
function render(
|
|
element: Element<ElementType>,
|
|
containerTag: number,
|
|
callback: ?() => void,
|
|
): ?ElementRef<ElementType> {
|
|
let root = roots.get(containerTag);
|
|
|
|
if (!root) {
|
|
// 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(
|
|
containerTag,
|
|
LegacyRoot,
|
|
null,
|
|
false,
|
|
null,
|
|
'',
|
|
nativeOnUncaughtError,
|
|
nativeOnCaughtError,
|
|
defaultOnRecoverableError,
|
|
null,
|
|
);
|
|
roots.set(containerTag, root);
|
|
}
|
|
updateContainer(element, root, null, callback);
|
|
|
|
return getPublicRootInstance(root);
|
|
}
|
|
|
|
function unmountComponentAtNode(containerTag: number) {
|
|
const root = roots.get(containerTag);
|
|
if (root) {
|
|
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
|
|
updateContainer(null, root, null, () => {
|
|
roots.delete(containerTag);
|
|
});
|
|
}
|
|
}
|
|
|
|
function unmountComponentAtNodeAndRemoveContainer(containerTag: number) {
|
|
unmountComponentAtNode(containerTag);
|
|
|
|
// Call back into native to remove all of the subviews from this container
|
|
UIManager.removeRootView(containerTag);
|
|
}
|
|
|
|
function createPortal(
|
|
children: ReactNodeList,
|
|
containerTag: number,
|
|
key: ?string = null,
|
|
): ReactPortal {
|
|
return createPortalImpl(children, containerTag, null, key);
|
|
}
|
|
|
|
setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
|
|
|
|
function computeComponentStackForErrorReporting(reactTag: number): string {
|
|
const fiber = getClosestInstanceFromNode(reactTag);
|
|
if (!fiber) {
|
|
return '';
|
|
}
|
|
return getStackByFiberInDevAndProd(fiber);
|
|
}
|
|
|
|
const roots = new Map<number, FiberRoot>();
|
|
|
|
const Internals = {
|
|
computeComponentStackForErrorReporting,
|
|
};
|
|
|
|
export {
|
|
// This is needed for implementation details of TouchableNativeFeedback
|
|
// Remove this once TouchableNativeFeedback doesn't use cloneElement
|
|
findHostInstance_DEPRECATED,
|
|
findNodeHandle,
|
|
dispatchCommand,
|
|
sendAccessibilityEvent,
|
|
render,
|
|
unmountComponentAtNode,
|
|
unmountComponentAtNodeAndRemoveContainer,
|
|
createPortal,
|
|
batchedUpdates as unstable_batchedUpdates,
|
|
Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
|
// This export is typically undefined in production builds.
|
|
// See the "enableGetInspectorDataForInstanceInProduction" flag.
|
|
getInspectorDataForInstance,
|
|
// DEV-only:
|
|
isChildPublicInstance,
|
|
};
|
|
|
|
injectIntoDevTools({
|
|
findFiberByHostInstance: getClosestInstanceFromNode,
|
|
bundleType: __DEV__ ? 1 : 0,
|
|
version: ReactVersion,
|
|
rendererPackageName: 'react-native-renderer',
|
|
rendererConfig: {
|
|
getInspectorDataForInstance,
|
|
getInspectorDataForViewTag: getInspectorDataForViewTag,
|
|
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
|
|
null,
|
|
findNodeHandle,
|
|
),
|
|
},
|
|
});
|