Show more meaningful error stack in ReactNative redbox

Reviewed By: yungsters

Differential Revision: D4797372

fbshipit-source-id: 069c013bcc3d58dd38a25979f4a04aed5fc1dde6
This commit is contained in:
Brian Vaughn 2017-03-30 17:06:51 -07:00 committed by Facebook Github Bot
parent 7db16a4020
commit d4aa42a4ac
4 changed files with 86 additions and 6 deletions

View File

@ -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.

View File

@ -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;

View File

@ -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(

View File

@ -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 {