Skip to content

Commit

Permalink
Add unstable_enableLogBox
Browse files Browse the repository at this point in the history
Summary:
This diff adds a new `unstable_enableLogBox` function to opt-into the new LogBox experience. If LogBox is not enabled early enough, we show an error with instructions.

With this, LogBox can be enabled with:

```
require('react-native').unstable_enableLogBox();
```

Changelog: [General] [Adds] unstable_enableLogBox

Reviewed By: zackargyle, rubennorte

Differential Revision: D18808940

fbshipit-source-id: 4b0234ddc4d1646515bf63110d5b02133780512e
  • Loading branch information
rickhanlonii authored and facebook-github-bot committed Dec 10, 2019
1 parent 5879795 commit dd8e5f4
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Libraries/Core/ExceptionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function reportException(e: ExtendedError, isFatal: boolean) {
e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`;

const isHandledByLogBox =
e.forceRedbox !== true && global.__reactExperimentalLogBox;
e.forceRedbox !== true && global.__unstable_isLogBoxEnabled === true;

const data = preprocessException({
message,
Expand Down
30 changes: 11 additions & 19 deletions Libraries/ReactNative/AppContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,14 @@ class AppContainer extends React.Component<Props, State> {
}

render(): React.Node {
let logBox = null;
if (__DEV__ && !this.props.internal_excludeLogBox) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
.default;
logBox = <LogBoxNotificationContainer />;
} else {
const YellowBox = require('../YellowBox/YellowBox');
logBox = <YellowBox />;
}
let yellowBox = null;
if (__DEV__) {
if (
!global.__RCTProfileIsProfiling &&
!this.props.internal_excludeLogBox
) {
const YellowBox = require('../YellowBox/YellowBox');
yellowBox = <YellowBox />;
}
}

Expand Down Expand Up @@ -138,7 +135,7 @@ class AppContainer extends React.Component<Props, State> {
<View style={styles.appContainer} pointerEvents="box-none">
{!this.state.hasError && innerView}
{this.state.inspector}
{logBox}
{yellowBox}
</View>
</RootTagContext.Provider>
);
Expand All @@ -153,13 +150,8 @@ const styles = StyleSheet.create({

if (__DEV__) {
if (!global.__RCTProfileIsProfiling) {
if (global.__reactExperimentalLogBox) {
const LogBox = require('../LogBox/LogBox');
LogBox.install();
} else {
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
const YellowBox = require('../YellowBox/YellowBox');
YellowBox.install();
}
}

Expand Down
16 changes: 9 additions & 7 deletions Libraries/ReactNative/AppRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,15 @@ const AppRegistry = {
* See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
*/
runApplication(appKey: string, appParameters: any): void {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
if (appKey !== 'LogBox') {
const msg =
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
infoLog(msg);
BugReporting.addSource(
'AppRegistry.runApplication' + runCount++,
() => msg,
);
}
invariant(
runnables[appKey] && runnables[appKey].run,
`"${appKey}" has not been registered. This can happen if:\n` +
Expand Down
77 changes: 42 additions & 35 deletions Libraries/YellowBox/YellowBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@

const React = require('react');

import type {Category} from './Data/YellowBoxCategory';
import type {
Registry,
Subscription,
IgnorePattern,
} from './Data/YellowBoxRegistry';
import type {Registry, IgnorePattern} from './Data/YellowBoxRegistry';
import * as LogBoxData from '../LogBox/Data/LogBoxData';
type Props = $ReadOnly<{||}>;
type State = {|
Expand Down Expand Up @@ -47,14 +42,19 @@ let YellowBox;
if (__DEV__) {
const Platform = require('../Utilities/Platform');
const RCTLog = require('../Utilities/RCTLog');
const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxContainer = require('./YellowBoxContainer').default;
const LogBox = require('../LogBox/LogBox');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
.default;

// YellowBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error;
let warnImpl = warn;
let _isLogBoxEnabled = false;
let _isInstalled = false;
(console: any).error = function(...args) {
errorImpl(...args);
};
Expand All @@ -70,6 +70,11 @@ if (__DEV__) {
}

static install(): void {
_isInstalled = true;
if (_isLogBoxEnabled) {
LogBox.install();
return;
}
errorImpl = function(...args) {
error.call(console, ...args);
// Show YellowBox for the `warning` module.
Expand Down Expand Up @@ -102,46 +107,39 @@ if (__DEV__) {
}

static uninstall(): void {
if (_isLogBoxEnabled) {
LogBox.uninstall();
return;
}
_isInstalled = false;
errorImpl = error;
warnImpl = warn;
delete (console: any).disableYellowBox;
}

_subscription: ?Subscription;

state = {
registry: null,
};
static __unstable_enableLogBox(): void {
if (_isInstalled) {
throw new Error(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
}
_isLogBoxEnabled = true;

render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
// TODO: Temporary hack to prevent cycles with the ExceptionManager.
global.__unstable_isLogBoxEnabled = true;
}

componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
static __unstable_isLogBoxEnabled(): boolean {
return !!_isLogBoxEnabled;
}

componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
render(): React.Node {
if (_isLogBoxEnabled) {
return <LogBoxNotificationContainer />;
}
}

_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};

_handleDismissAll(): void {
YellowBoxRegistry.clear();
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return <YellowBoxContainer />;
}
};

Expand All @@ -162,6 +160,13 @@ if (__DEV__) {
// Do nothing.
}

static __unstable_enableLogBox(): void {
// Do nothing.
}
static __unstable_isLogBoxEnabled(): boolean {
return false;
}

render(): React.Node {
return null;
}
Expand All @@ -172,5 +177,7 @@ module.exports = (YellowBox: Class<React.Component<Props, State>> & {
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
__unstable_enableLogBox(): void,
__unstable_isLogBoxEnabled(): boolean,
...
});
65 changes: 65 additions & 0 deletions Libraries/YellowBox/YellowBoxContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 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';

const React = require('react');

import type {Category} from './Data/YellowBoxCategory';
import type {Registry, Subscription} from './Data/YellowBoxRegistry';

type Props = $ReadOnly<{||}>;
type State = $ReadOnly<{|
registry: ?Registry,
|}>;

const YellowBoxList = require('./UI/YellowBoxList');
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');

class YellowBoxContainer extends React.Component<Props, State> {
_subscription: ?Subscription;

state: State = {
registry: null,
};

render(): React.Node {
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
return this.state.registry == null ? null : (
<YellowBoxList
onDismiss={this._handleDismiss}
onDismissAll={this._handleDismissAll}
registry={this.state.registry}
/>
);
}

componentDidMount(): void {
this._subscription = YellowBoxRegistry.observe(registry => {
this.setState({registry});
});
}

componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}

_handleDismiss = (category: Category): void => {
YellowBoxRegistry.delete(category);
};

_handleDismissAll(): void {
YellowBoxRegistry.clear();
}
}

export default YellowBoxContainer;
53 changes: 53 additions & 0 deletions Libraries/YellowBox/__tests__/YellowBox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,16 @@

'use strict';

import * as React from 'react';
const YellowBox = require('../YellowBox');
const YellowBoxRegistry = require('../Data/YellowBoxRegistry');
const LogBoxData = require('../../LogBox/Data/LogBoxData');
const render = require('../../../jest/renderer');

jest.mock('../../LogBox/LogBoxNotificationContainer', () => ({
__esModule: true,
default: 'LogBoxNotificationContainer',
}));

describe('YellowBox', () => {
const {error, warn} = console;
Expand Down Expand Up @@ -74,4 +82,49 @@ describe('YellowBox', () => {
(console: any).error('Warning: ...');
expect(YellowBoxRegistry.add).toBeCalled();
});

it('if LogBox is enabled, installs and uninstalls LogBox', () => {
jest.mock('../../LogBox/Data/LogBoxData');
jest.mock('../Data/YellowBoxRegistry');

YellowBox.__unstable_enableLogBox();
YellowBox.install();

(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);

YellowBox.uninstall();
(LogBoxData.addLog: any).mockClear();

(console: any).warn('Some warning');
expect(YellowBoxRegistry.add).not.toBeCalled();
expect(LogBoxData.addLog).not.toBeCalled();
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
});

it('throws if LogBox is enabled after YellowBox is installed', () => {
jest.mock('../Data/YellowBoxRegistry');

YellowBox.install();

expect(() => YellowBox.__unstable_enableLogBox()).toThrow(
'LogBox must be enabled before AppContainer is required so that it can properly wrap the console methods.\n\nPlease enable LogBox earlier in your app.\n\n',
);
});

it('should render YellowBoxContainer by default', () => {
const output = render.shallowRender(<YellowBox />);

expect(output).toMatchSnapshot();
});

it('should render LogBoxNotificationContainer when LogBox is enabled', () => {
YellowBox.__unstable_enableLogBox();

const output = render.shallowRender(<YellowBox />);

expect(output).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`YellowBox should render LogBoxNotificationContainer when LogBox is enabled 1`] = `<LogBoxNotificationContainer />`;

exports[`YellowBox should render YellowBoxContainer by default 1`] = `<YellowBoxContainer />`;
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ module.exports = {
get unstable_RootTagContext(): RootTagContext {
return require('./Libraries/ReactNative/RootTagContext');
},
get unstable_enableLogBox(): () => void {
return require('./Libraries/YellowBox/YellowBox').__unstable_enableLogBox;
},

// Prop Types
get ColorPropType(): DeprecatedColorPropType {
Expand Down

0 comments on commit dd8e5f4

Please sign in to comment.