Skip to content

Commit dd8e5f4

Browse files
rickhanloniifacebook-github-bot
authored andcommitted
Add unstable_enableLogBox
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
1 parent 5879795 commit dd8e5f4

File tree

8 files changed

+189
-62
lines changed

8 files changed

+189
-62
lines changed

Libraries/Core/ExceptionsManager.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ function reportException(e: ExtendedError, isFatal: boolean) {
8383
e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`;
8484

8585
const isHandledByLogBox =
86-
e.forceRedbox !== true && global.__reactExperimentalLogBox;
86+
e.forceRedbox !== true && global.__unstable_isLogBoxEnabled === true;
8787

8888
const data = preprocessException({
8989
message,

Libraries/ReactNative/AppContainer.js

+11-19
Original file line numberDiff line numberDiff line change
@@ -94,17 +94,14 @@ class AppContainer extends React.Component<Props, State> {
9494
}
9595

9696
render(): React.Node {
97-
let logBox = null;
98-
if (__DEV__ && !this.props.internal_excludeLogBox) {
99-
if (!global.__RCTProfileIsProfiling) {
100-
if (global.__reactExperimentalLogBox) {
101-
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
102-
.default;
103-
logBox = <LogBoxNotificationContainer />;
104-
} else {
105-
const YellowBox = require('../YellowBox/YellowBox');
106-
logBox = <YellowBox />;
107-
}
97+
let yellowBox = null;
98+
if (__DEV__) {
99+
if (
100+
!global.__RCTProfileIsProfiling &&
101+
!this.props.internal_excludeLogBox
102+
) {
103+
const YellowBox = require('../YellowBox/YellowBox');
104+
yellowBox = <YellowBox />;
108105
}
109106
}
110107

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

154151
if (__DEV__) {
155152
if (!global.__RCTProfileIsProfiling) {
156-
if (global.__reactExperimentalLogBox) {
157-
const LogBox = require('../LogBox/LogBox');
158-
LogBox.install();
159-
} else {
160-
const YellowBox = require('../YellowBox/YellowBox');
161-
YellowBox.install();
162-
}
153+
const YellowBox = require('../YellowBox/YellowBox');
154+
YellowBox.install();
163155
}
164156
}
165157

Libraries/ReactNative/AppRegistry.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,15 @@ const AppRegistry = {
181181
* See http://facebook.github.io/react-native/docs/appregistry.html#runapplication
182182
*/
183183
runApplication(appKey: string, appParameters: any): void {
184-
const msg =
185-
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
186-
infoLog(msg);
187-
BugReporting.addSource(
188-
'AppRegistry.runApplication' + runCount++,
189-
() => msg,
190-
);
184+
if (appKey !== 'LogBox') {
185+
const msg =
186+
'Running "' + appKey + '" with ' + JSON.stringify(appParameters);
187+
infoLog(msg);
188+
BugReporting.addSource(
189+
'AppRegistry.runApplication' + runCount++,
190+
() => msg,
191+
);
192+
}
191193
invariant(
192194
runnables[appKey] && runnables[appKey].run,
193195
`"${appKey}" has not been registered. This can happen if:\n` +

Libraries/YellowBox/YellowBox.js

+42-35
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212

1313
const React = require('react');
1414

15-
import type {Category} from './Data/YellowBoxCategory';
16-
import type {
17-
Registry,
18-
Subscription,
19-
IgnorePattern,
20-
} from './Data/YellowBoxRegistry';
15+
import type {Registry, IgnorePattern} from './Data/YellowBoxRegistry';
2116
import * as LogBoxData from '../LogBox/Data/LogBoxData';
2217
type Props = $ReadOnly<{||}>;
2318
type State = {|
@@ -47,14 +42,19 @@ let YellowBox;
4742
if (__DEV__) {
4843
const Platform = require('../Utilities/Platform');
4944
const RCTLog = require('../Utilities/RCTLog');
50-
const YellowBoxList = require('./UI/YellowBoxList');
45+
const YellowBoxContainer = require('./YellowBoxContainer').default;
46+
const LogBox = require('../LogBox/LogBox');
5147
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
48+
const LogBoxNotificationContainer = require('../LogBox/LogBoxNotificationContainer')
49+
.default;
5250

5351
// YellowBox needs to insert itself early,
5452
// in order to access the component stacks appended by React DevTools.
5553
const {error, warn} = console;
5654
let errorImpl = error;
5755
let warnImpl = warn;
56+
let _isLogBoxEnabled = false;
57+
let _isInstalled = false;
5858
(console: any).error = function(...args) {
5959
errorImpl(...args);
6060
};
@@ -70,6 +70,11 @@ if (__DEV__) {
7070
}
7171

7272
static install(): void {
73+
_isInstalled = true;
74+
if (_isLogBoxEnabled) {
75+
LogBox.install();
76+
return;
77+
}
7378
errorImpl = function(...args) {
7479
error.call(console, ...args);
7580
// Show YellowBox for the `warning` module.
@@ -102,46 +107,39 @@ if (__DEV__) {
102107
}
103108

104109
static uninstall(): void {
110+
if (_isLogBoxEnabled) {
111+
LogBox.uninstall();
112+
return;
113+
}
114+
_isInstalled = false;
105115
errorImpl = error;
106116
warnImpl = warn;
107117
delete (console: any).disableYellowBox;
108118
}
109119

110-
_subscription: ?Subscription;
111-
112-
state = {
113-
registry: null,
114-
};
120+
static __unstable_enableLogBox(): void {
121+
if (_isInstalled) {
122+
throw new Error(
123+
'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',
124+
);
125+
}
126+
_isLogBoxEnabled = true;
115127

116-
render(): React.Node {
117-
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
118-
return this.state.registry == null ? null : (
119-
<YellowBoxList
120-
onDismiss={this._handleDismiss}
121-
onDismissAll={this._handleDismissAll}
122-
registry={this.state.registry}
123-
/>
124-
);
128+
// TODO: Temporary hack to prevent cycles with the ExceptionManager.
129+
global.__unstable_isLogBoxEnabled = true;
125130
}
126131

127-
componentDidMount(): void {
128-
this._subscription = YellowBoxRegistry.observe(registry => {
129-
this.setState({registry});
130-
});
132+
static __unstable_isLogBoxEnabled(): boolean {
133+
return !!_isLogBoxEnabled;
131134
}
132135

133-
componentWillUnmount(): void {
134-
if (this._subscription != null) {
135-
this._subscription.unsubscribe();
136+
render(): React.Node {
137+
if (_isLogBoxEnabled) {
138+
return <LogBoxNotificationContainer />;
136139
}
137-
}
138-
139-
_handleDismiss = (category: Category): void => {
140-
YellowBoxRegistry.delete(category);
141-
};
142140

143-
_handleDismissAll(): void {
144-
YellowBoxRegistry.clear();
141+
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
142+
return <YellowBoxContainer />;
145143
}
146144
};
147145

@@ -162,6 +160,13 @@ if (__DEV__) {
162160
// Do nothing.
163161
}
164162

163+
static __unstable_enableLogBox(): void {
164+
// Do nothing.
165+
}
166+
static __unstable_isLogBoxEnabled(): boolean {
167+
return false;
168+
}
169+
165170
render(): React.Node {
166171
return null;
167172
}
@@ -172,5 +177,7 @@ module.exports = (YellowBox: Class<React.Component<Props, State>> & {
172177
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
173178
install(): void,
174179
uninstall(): void,
180+
__unstable_enableLogBox(): void,
181+
__unstable_isLogBoxEnabled(): boolean,
175182
...
176183
});
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
const React = require('react');
14+
15+
import type {Category} from './Data/YellowBoxCategory';
16+
import type {Registry, Subscription} from './Data/YellowBoxRegistry';
17+
18+
type Props = $ReadOnly<{||}>;
19+
type State = $ReadOnly<{|
20+
registry: ?Registry,
21+
|}>;
22+
23+
const YellowBoxList = require('./UI/YellowBoxList');
24+
const YellowBoxRegistry = require('./Data/YellowBoxRegistry');
25+
26+
class YellowBoxContainer extends React.Component<Props, State> {
27+
_subscription: ?Subscription;
28+
29+
state: State = {
30+
registry: null,
31+
};
32+
33+
render(): React.Node {
34+
// TODO: Ignore warnings that fire when rendering `YellowBox` itself.
35+
return this.state.registry == null ? null : (
36+
<YellowBoxList
37+
onDismiss={this._handleDismiss}
38+
onDismissAll={this._handleDismissAll}
39+
registry={this.state.registry}
40+
/>
41+
);
42+
}
43+
44+
componentDidMount(): void {
45+
this._subscription = YellowBoxRegistry.observe(registry => {
46+
this.setState({registry});
47+
});
48+
}
49+
50+
componentWillUnmount(): void {
51+
if (this._subscription != null) {
52+
this._subscription.unsubscribe();
53+
}
54+
}
55+
56+
_handleDismiss = (category: Category): void => {
57+
YellowBoxRegistry.delete(category);
58+
};
59+
60+
_handleDismissAll(): void {
61+
YellowBoxRegistry.clear();
62+
}
63+
}
64+
65+
export default YellowBoxContainer;

Libraries/YellowBox/__tests__/YellowBox-test.js

+53
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,16 @@
1111

1212
'use strict';
1313

14+
import * as React from 'react';
1415
const YellowBox = require('../YellowBox');
1516
const YellowBoxRegistry = require('../Data/YellowBoxRegistry');
17+
const LogBoxData = require('../../LogBox/Data/LogBoxData');
18+
const render = require('../../../jest/renderer');
19+
20+
jest.mock('../../LogBox/LogBoxNotificationContainer', () => ({
21+
__esModule: true,
22+
default: 'LogBoxNotificationContainer',
23+
}));
1624

1725
describe('YellowBox', () => {
1826
const {error, warn} = console;
@@ -74,4 +82,49 @@ describe('YellowBox', () => {
7482
(console: any).error('Warning: ...');
7583
expect(YellowBoxRegistry.add).toBeCalled();
7684
});
85+
86+
it('if LogBox is enabled, installs and uninstalls LogBox', () => {
87+
jest.mock('../../LogBox/Data/LogBoxData');
88+
jest.mock('../Data/YellowBoxRegistry');
89+
90+
YellowBox.__unstable_enableLogBox();
91+
YellowBox.install();
92+
93+
(console: any).warn('Some warning');
94+
expect(YellowBoxRegistry.add).not.toBeCalled();
95+
expect(LogBoxData.addLog).toBeCalled();
96+
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
97+
98+
YellowBox.uninstall();
99+
(LogBoxData.addLog: any).mockClear();
100+
101+
(console: any).warn('Some warning');
102+
expect(YellowBoxRegistry.add).not.toBeCalled();
103+
expect(LogBoxData.addLog).not.toBeCalled();
104+
expect(YellowBox.__unstable_isLogBoxEnabled()).toBe(true);
105+
});
106+
107+
it('throws if LogBox is enabled after YellowBox is installed', () => {
108+
jest.mock('../Data/YellowBoxRegistry');
109+
110+
YellowBox.install();
111+
112+
expect(() => YellowBox.__unstable_enableLogBox()).toThrow(
113+
'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',
114+
);
115+
});
116+
117+
it('should render YellowBoxContainer by default', () => {
118+
const output = render.shallowRender(<YellowBox />);
119+
120+
expect(output).toMatchSnapshot();
121+
});
122+
123+
it('should render LogBoxNotificationContainer when LogBox is enabled', () => {
124+
YellowBox.__unstable_enableLogBox();
125+
126+
const output = render.shallowRender(<YellowBox />);
127+
128+
expect(output).toMatchSnapshot();
129+
});
77130
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`YellowBox should render LogBoxNotificationContainer when LogBox is enabled 1`] = `<LogBoxNotificationContainer />`;
4+
5+
exports[`YellowBox should render YellowBoxContainer by default 1`] = `<YellowBoxContainer />`;

index.js

+3
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,9 @@ module.exports = {
438438
get unstable_RootTagContext(): RootTagContext {
439439
return require('./Libraries/ReactNative/RootTagContext');
440440
},
441+
get unstable_enableLogBox(): () => void {
442+
return require('./Libraries/YellowBox/YellowBox').__unstable_enableLogBox;
443+
},
441444

442445
// Prop Types
443446
get ColorPropType(): DeprecatedColorPropType {

0 commit comments

Comments
 (0)