Show more meaningful error stack in ReactNative redbox
Reviewed By: yungsters Differential Revision: D4797372 fbshipit-source-id: 069c013bcc3d58dd38a25979f4a04aed5fc1dde6
This commit is contained in:
parent
7db16a4020
commit
d4aa42a4ac
|
@ -12,10 +12,12 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const ReactFiberErrorLogger = require('ReactFiberErrorLogger');
|
||||||
const ReactFiberReconciler = require('ReactFiberReconciler');
|
const ReactFiberReconciler = require('ReactFiberReconciler');
|
||||||
const ReactGenericBatching = require('ReactGenericBatching');
|
const ReactGenericBatching = require('ReactGenericBatching');
|
||||||
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
const ReactNativeAttributePayload = require('ReactNativeAttributePayload');
|
||||||
const ReactNativeComponentTree = require('ReactNativeComponentTree');
|
const ReactNativeComponentTree = require('ReactNativeComponentTree');
|
||||||
|
const ReactNativeFiberErrorDialog = require('ReactNativeFiberErrorDialog');
|
||||||
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
|
const ReactNativeFiberHostComponent = require('ReactNativeFiberHostComponent');
|
||||||
const ReactNativeInjection = require('ReactNativeInjection');
|
const ReactNativeInjection = require('ReactNativeInjection');
|
||||||
const ReactNativeTagHandles = require('ReactNativeTagHandles');
|
const ReactNativeTagHandles = require('ReactNativeTagHandles');
|
||||||
|
@ -377,6 +379,13 @@ findNodeHandle.injection.injectFindNode((fiber: Fiber) =>
|
||||||
NativeRenderer.findHostInstance(fiber));
|
NativeRenderer.findHostInstance(fiber));
|
||||||
findNodeHandle.injection.injectFindRootNodeID(instance => instance);
|
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 = {
|
const ReactNative = {
|
||||||
// External users of findNodeHandle() expect the host tag number return type.
|
// External users of findNodeHandle() expect the host tag number return type.
|
||||||
// The injected findNodeHandle() strategy returns the instance wrapper though.
|
// 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';
|
'use strict';
|
||||||
|
|
||||||
const emptyFunction = require('fbjs/lib/emptyFunction');
|
|
||||||
const invariant = require('fbjs/lib/invariant');
|
const invariant = require('fbjs/lib/invariant');
|
||||||
|
|
||||||
import type {CapturedError} from 'ReactFiberScheduler';
|
import type {CapturedError} from 'ReactFiberScheduler';
|
||||||
|
|
||||||
let showDialog = emptyFunction;
|
const defaultShowDialog = () => true;
|
||||||
|
|
||||||
|
let showDialog = defaultShowDialog;
|
||||||
|
|
||||||
function logCapturedError(capturedError: CapturedError): void {
|
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__) {
|
if (__DEV__) {
|
||||||
const {
|
const {
|
||||||
componentName,
|
componentName,
|
||||||
|
@ -85,14 +94,16 @@ function logCapturedError(capturedError: CapturedError): void {
|
||||||
`React caught an error thrown by one of your components.\n\n${error.stack}`,
|
`React caught an error thrown by one of your components.\n\n${error.stack}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
showDialog(capturedError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.injection = {
|
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(
|
invariant(
|
||||||
showDialog === emptyFunction,
|
showDialog === defaultShowDialog,
|
||||||
'The custom dialog was already injected.',
|
'The custom dialog was already injected.',
|
||||||
);
|
);
|
||||||
invariant(
|
invariant(
|
||||||
|
|
|
@ -684,6 +684,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Without this explicit null return Flow complains of invalid return type
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
|
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||||
|
|
Loading…
Reference in New Issue