mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
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.
203 lines
6.1 KiB
JavaScript
203 lines
6.1 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 './ReactFabricInjection';
|
|
|
|
import {
|
|
batchedUpdates as batchedUpdatesImpl,
|
|
discreteUpdates,
|
|
createContainer,
|
|
updateContainer,
|
|
injectIntoDevTools,
|
|
getPublicRootInstance,
|
|
defaultOnUncaughtError,
|
|
defaultOnCaughtError,
|
|
defaultOnRecoverableError,
|
|
} from 'react-reconciler/src/ReactFiberReconciler';
|
|
|
|
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
|
|
import {setBatchingImplementation} from './legacy-events/ReactGenericBatching';
|
|
import ReactVersion from 'shared/ReactVersion';
|
|
|
|
import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
|
|
import {
|
|
getInspectorDataForViewTag,
|
|
getInspectorDataForViewAtPoint,
|
|
getInspectorDataForInstance,
|
|
} from './ReactNativeFiberInspector';
|
|
import {LegacyRoot, ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
|
|
import {
|
|
findHostInstance_DEPRECATED,
|
|
findNodeHandle,
|
|
dispatchCommand,
|
|
sendAccessibilityEvent,
|
|
getNodeFromInternalInstanceHandle,
|
|
isChildPublicInstance,
|
|
} from './ReactNativePublicCompat';
|
|
import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFabric';
|
|
|
|
// 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,
|
|
concurrentRoot: ?boolean,
|
|
): ?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,
|
|
concurrentRoot ? ConcurrentRoot : LegacyRoot,
|
|
null,
|
|
false,
|
|
null,
|
|
'',
|
|
nativeOnUncaughtError,
|
|
nativeOnCaughtError,
|
|
defaultOnRecoverableError,
|
|
null,
|
|
);
|
|
roots.set(containerTag, root);
|
|
}
|
|
updateContainer(element, root, null, callback);
|
|
|
|
return getPublicRootInstance(root);
|
|
}
|
|
|
|
// $FlowFixMe[missing-this-annot]
|
|
function unmountComponentAtNode(containerTag: number) {
|
|
this.stopSurface(containerTag);
|
|
}
|
|
|
|
function stopSurface(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 createPortal(
|
|
children: ReactNodeList,
|
|
containerTag: number,
|
|
key: ?string = null,
|
|
): ReactPortal {
|
|
return createPortalImpl(children, containerTag, null, key);
|
|
}
|
|
|
|
setBatchingImplementation(batchedUpdatesImpl, discreteUpdates);
|
|
|
|
const roots = new Map<number, FiberRoot>();
|
|
|
|
export {
|
|
// This is needed for implementation details of TouchableNativeFeedback
|
|
// Remove this once TouchableNativeFeedback doesn't use cloneElement
|
|
findHostInstance_DEPRECATED,
|
|
findNodeHandle,
|
|
dispatchCommand,
|
|
sendAccessibilityEvent,
|
|
render,
|
|
// Deprecated - this function is being renamed to stopSurface, use that instead.
|
|
// TODO (T47576999): Delete this once it's no longer called from native code.
|
|
unmountComponentAtNode,
|
|
stopSurface,
|
|
createPortal,
|
|
// This export is typically undefined in production builds.
|
|
// See the "enableGetInspectorDataForInstanceInProduction" flag.
|
|
getInspectorDataForInstance,
|
|
// The public instance has a reference to the internal instance handle.
|
|
// This method allows it to acess the most recent shadow node for
|
|
// the instance (it's only accessible through it).
|
|
getNodeFromInternalInstanceHandle,
|
|
// Fabric native methods to traverse the host tree return the same internal
|
|
// instance handles we use to dispatch events. This provides a way to access
|
|
// the public instances we created from them (potentially created lazily).
|
|
getPublicInstanceFromInternalInstanceHandle,
|
|
// DEV-only:
|
|
isChildPublicInstance,
|
|
};
|
|
|
|
injectIntoDevTools({
|
|
// $FlowExpectedError[incompatible-call] The type of `Instance` in `getClosestInstanceFromNode` does not match in Fabric and the legacy renderer, so it fails to typecheck here.
|
|
findFiberByHostInstance: getClosestInstanceFromNode,
|
|
bundleType: __DEV__ ? 1 : 0,
|
|
version: ReactVersion,
|
|
rendererPackageName: 'react-native-renderer',
|
|
rendererConfig: {
|
|
getInspectorDataForInstance,
|
|
getInspectorDataForViewTag: getInspectorDataForViewTag,
|
|
getInspectorDataForViewAtPoint: getInspectorDataForViewAtPoint.bind(
|
|
null,
|
|
findNodeHandle,
|
|
),
|
|
},
|
|
});
|