Files
react-native/Libraries/LogBox/LogBox.js
T
Rick Hanlon 5eddf1d79a LogBox - Show native redbox when failing to handle logs in LogBox
Summary:
This diff adds error handling to logbox so that if there is an error either when parsing logs or when rendering LogBox, we show a native redbox with the error that was thrown and a message explaining that it's an internal React Native error.

Changelog: [Internal]

Reviewed By: cpojer

Differential Revision: D18394788

fbshipit-source-id: 5d74d58e4b28ef6d863079e83677fb23ef4ccb34
2019-11-08 07:41:09 -08:00

205 lines
5.2 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/
'use strict';
import * as React from 'react';
import Platform from '../Utilities/Platform';
import RCTLog from '../Utilities/RCTLog';
import LogBoxContainer from './UI/LogBoxContainer';
import * as LogBoxData from './Data/LogBoxData';
import {parseLogBoxLog} from './Data/parseLogBoxLog';
import type {LogBoxLogs, Subscription, IgnorePattern} from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
type Props = $ReadOnly<{||}>;
type State = {|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
|};
let LogBoxComponent;
/**
* LogBox displays logs in the app.
*/
if (__DEV__) {
// LogBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error.bind(console);
let warnImpl = warn.bind(console);
(console: any).error = function(...args) {
errorImpl(...args);
};
(console: any).warn = function(...args) {
warnImpl(...args);
};
LogBoxComponent = class LogBox extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err, errorInfo) {
LogBoxData.reportLogBoxError(err, errorInfo.componentStack);
}
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
LogBox.ignoreLogs(patterns);
}
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
LogBoxData.addIgnorePatterns(patterns);
}
static install(): void {
errorImpl = function(...args) {
error.call(console, ...args);
// Show LogBox for the `warning` module.
if (typeof args[0] === 'string' && args[0].startsWith('Warning: ')) {
registerWarning(...args);
}
};
warnImpl = function(...args) {
registerWarning(...args);
};
if ((console: any).disableLogBox === true) {
LogBoxData.setDisabled(true);
}
(Object.defineProperty: any)(console, 'disableLogBox', {
configurable: true,
get: () => LogBoxData.isDisabled(),
set: value => LogBoxData.setDisabled(value),
});
if (Platform.isTesting) {
(console: any).disableLogBox = true;
}
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
}
static uninstall(): void {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
}
_subscription: ?Subscription;
state = {
logs: new Set(),
isDisabled: false,
hasError: false,
};
render(): React.Node {
if (this.state.hasError) {
// This happens when the component failed to render, in which case we delegate to the native redbox.
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
return null;
}
return this.state.logs == null ? null : (
<LogBoxContainer
onDismiss={this._handleDismiss}
onDismissWarns={LogBoxData.clearWarnings}
onDismissErrors={LogBoxData.clearErrors}
logs={this.state.logs}
isDisabled={this.state.isDisabled}
/>
);
}
componentDidMount(): void {
this._subscription = LogBoxData.observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss(log: LogBoxLog): void {
LogBoxData.dismiss(log);
}
};
const registerWarning = (...args): void => {
try {
// This is carried over from the old YellowBox, but it is not clear why.
if (typeof args[0] !== 'string' || !args[0].startsWith('(ADVICE)')) {
const {category, message, componentStack} = parseLogBoxLog(args);
if (!LogBoxData.isMessageIgnored(message.content)) {
// Be sure to pass LogBox warnings through.
warn.call(console, ...args);
if (!LogBoxData.isLogBoxErrorMessage(message.content)) {
LogBoxData.addLog({
level: 'warn',
category,
message,
componentStack,
});
}
}
}
} catch (err) {
LogBoxData.reportLogBoxError(err);
}
};
} else {
LogBoxComponent = class extends React.Component<Props, State> {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static install(): void {
// Do nothing.
}
static uninstall(): void {
// Do nothing.
}
render(): React.Node {
return null;
}
};
}
module.exports = (LogBoxComponent: Class<React.Component<Props, State>> & {
// TODO: deprecated, replace with ignoreLogs
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
});