mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Show more meanignful stack trace for ReactNative errors (#9291)
* Show more meanignful stack trace for ReactNative errors Clicking on the stack should jump to where the error actually occurred rather than to where it's logged in a redbox component. * Made showDialog() return type stricter. Added mock and Flow types for ExceptionManager * Prettier * Handle null/string thrown errors * Removed unused reference to emptyFunction
This commit is contained in:
Vendored
+6
@@ -26,6 +26,12 @@ declare module 'TextInputState' {
|
||||
declare function blurTextInput(object : any) : void;
|
||||
declare function focusTextInput(object : any) : void;
|
||||
}
|
||||
declare module 'ExceptionsManager' {
|
||||
declare function handleException(
|
||||
error: Error,
|
||||
isFatal: boolean,
|
||||
) : void;
|
||||
}
|
||||
declare module 'UIManager' {
|
||||
declare var customBubblingEventTypes : Object;
|
||||
declare var customDirectEventTypes : Object;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
handleException: jest.fn(),
|
||||
};
|
||||
@@ -12,10 +12,12 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const ReactFiberErrorLogger = require('ReactFiberErrorLogger');
|
||||
const ReactFiberReconciler = require('ReactFiberReconciler');
|
||||
const ReactGenericBatching = require('ReactGenericBatching');
|
||||
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||
const ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||
const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog');
|
||||
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
|
||||
const ReactNativeInjection = require('ReactNativeInjection');
|
||||
const ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||
@@ -377,6 +379,12 @@ findNodeHandle.injection.injectFindNode((fiber: Fiber) =>
|
||||
NativeRenderer.findHostInstance(fiber));
|
||||
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
|
||||
|
||||
// Intercept lifecycle errors and ensure they are shown with the correct stack
|
||||
// trace within the native redbox component.
|
||||
ReactFiberErrorLogger.injection.injectDialog(
|
||||
ReactNativeFiberErrorDialog.showDialog,
|
||||
);
|
||||
|
||||
const ReactNative = {
|
||||
// External users of findNodeHandle() expect the host tag number return type.
|
||||
// The injected findNodeHandle() strategy returns the instance wrapper though.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeFiberErrorDialog
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ExceptionsManager = require('ExceptionsManager');
|
||||
|
||||
import type {CapturedError} from 'ReactFiberScheduler';
|
||||
|
||||
/**
|
||||
* Intercept lifecycle errors and ensure they are shown with the correct stack
|
||||
* trace within the native redbox component.
|
||||
*/
|
||||
function ReactNativeFiberErrorDialog(capturedError: CapturedError): boolean {
|
||||
const {componentStack, error} = capturedError;
|
||||
|
||||
let errorMessage: string;
|
||||
let errorStack: string;
|
||||
let errorType: Class<Error>;
|
||||
|
||||
// Typically Errors are thrown but eg strings or null can be thrown as well.
|
||||
if (error && typeof error === 'object') {
|
||||
const {message, name} = error;
|
||||
|
||||
const summary = message ? `${name}: ${message}` : name;
|
||||
|
||||
errorMessage = `${summary}\n\nThis error is located at:${componentStack}`;
|
||||
errorStack = error.stack;
|
||||
errorType = error.constructor;
|
||||
} else {
|
||||
errorMessage = `Unspecified error at:${componentStack}`;
|
||||
errorStack = '';
|
||||
errorType = Error;
|
||||
}
|
||||
|
||||
const newError = new errorType(errorMessage);
|
||||
newError.stack = errorStack;
|
||||
|
||||
ExceptionsManager.handleException(newError, false);
|
||||
|
||||
// Return false here to prevent ReactFiberErrorLogger default behavior of
|
||||
// logging error details to console.error. Calls to console.error are
|
||||
// automatically routed to the native redbox controller, which we've already
|
||||
// done above by calling ExceptionsManager.
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports.showDialog = ReactNativeFiberErrorDialog;
|
||||
@@ -12,14 +12,23 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {CapturedError} from 'ReactFiberScheduler';
|
||||
|
||||
let showDialog = emptyFunction;
|
||||
const defaultShowDialog = () => true;
|
||||
|
||||
let showDialog = defaultShowDialog;
|
||||
|
||||
function logCapturedError(capturedError: CapturedError): void {
|
||||
const logError = showDialog(capturedError);
|
||||
|
||||
// Allow injected showDialog() to prevent default console.error logging.
|
||||
// This enables renderers like ReactNative to better manage redbox behavior.
|
||||
if (logError === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const {
|
||||
componentName,
|
||||
@@ -85,14 +94,16 @@ function logCapturedError(capturedError: CapturedError): void {
|
||||
`React caught an error thrown by one of your components.\n\n${error.stack}`,
|
||||
);
|
||||
}
|
||||
|
||||
showDialog(capturedError);
|
||||
}
|
||||
|
||||
exports.injection = {
|
||||
injectDialog(fn: (e: CapturedError) => void) {
|
||||
/**
|
||||
* Display custom dialog for lifecycle errors.
|
||||
* Return false to prevent default behavior of logging to console.error.
|
||||
*/
|
||||
injectDialog(fn: (e: CapturedError) => boolean) {
|
||||
invariant(
|
||||
showDialog === emptyFunction,
|
||||
showDialog === defaultShowDialog,
|
||||
'The custom dialog was already injected.',
|
||||
);
|
||||
invariant(
|
||||
|
||||
Reference in New Issue
Block a user