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';
|
||||
|
||||
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,13 @@ 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(
|
||||
|
|
|
@ -684,6 +684,9 @@ module.exports = function<T, P, I, TI, PI, C, CX, PL>(
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Without this explicit null return Flow complains of invalid return type
|
||||
return null;
|
||||
}
|
||||
|
||||
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
|
||||
|
|
Loading…
Reference in New Issue