Detect React Native v3 backend and show warning
Brian Vaughn committed
Jul 18, 2019 at 16:24 UTC
249a2e043dbd261bf54a95d0893dd973f4489d57
7 files changed
+99
-9
packages/react-devtools-core/src/standalone.js
+1
@@ -86,6 +86,7 @@ function reload() {
86
bridge: ((bridge: any): Bridge),
87
showTabBar: true,
88
store: ((store: any): Store),
89
+ warnIfLegacyBackendDetected: true,
90
viewElementSourceFunction,
91
viewElementSourceRequiresFileLocation: true,
92
})
shells/dev/src/devtools.js
+1
@@ -77,6 +77,7 @@ inject('dist/app.js', () => {
77
browserTheme: 'light',
78
showTabBar: true,
79
store,
80
+ warnIfLegacyBackendDetected: true,
81
})
82
);
83
batch.then(() => {
src/bridge.js
+6
@@ -128,6 +128,12 @@ export default class Bridge extends EventEmitter<{|
128
}) || null;
129
}
130
131
+ // Listening directly to the wall isn't advised.
132
+ // It can be used to listen for legacy (v3) messages (since they use a different format).
133
+ get wall(): Wall {
134
+ return this._wall;
135
+ }
136
+
137
send(event: string, payload: any, transferable?: Array<any>) {
138
if (this._isShutdown) {
139
console.warn(
src/devtools/views/DevTools.js
+4
@@ -18,6 +18,7 @@ import ViewElementSourceContext from './Components/ViewElementSourceContext';
18
import { ProfilerContextController } from './Profiler/ProfilerContext';
19
import { ModalDialogContextController } from './ModalDialog';
20
import ReactLogo from './ReactLogo';
21
+import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
22
23
import styles from './DevTools.css';
24
@@ -38,6 +39,7 @@ export type Props = {|
39
defaultTab?: TabID,
40
showTabBar?: boolean,
41
store: Store,
42
+ warnIfLegacyBackendDetected?: boolean,
43
viewElementSourceFunction?: ?ViewElementSource,
44
viewElementSourceRequiresFileLocation?: boolean,
45
@@ -80,6 +82,7 @@ export default function DevTools({
82
settingsPortalContainer,
83
showTabBar = false,
84
store,
85
+ warnIfLegacyBackendDetected = false,
86
viewElementSourceFunction,
87
viewElementSourceRequiresFileLocation = false,
88
}: Props) {
@@ -143,6 +146,7 @@ export default function DevTools({
146
</TreeContextController>
147
</ViewElementSourceContext.Provider>
148
</SettingsContextController>
149
+ {warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
150
</ModalDialogContextController>
151
</StoreContext.Provider>
152
</BridgeContext.Provider>
src/devtools/views/ModalDialog.js
+21
-9
@@ -18,6 +18,7 @@ type DIALOG_ACTION_HIDE = {|
18
|};
19
type DIALOG_ACTION_SHOW = {|
20
type: 'SHOW',
21
+ canBeDismissed?: boolean,
22
content: React$Node,
23
title?: React$Node | null,
24
|};
@@ -27,6 +28,7 @@ type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
28
type Dispatch = (action: Action) => void;
29
30
type State = {|
31
+ canBeDismissed: boolean,
32
content: React$Node | null,
33
isVisible: boolean,
34
title: React$Node | null,
@@ -46,12 +48,14 @@ function dialogReducer(state, action) {
48
switch (action.type) {
49
case 'HIDE':
50
return {
51
+ canBeDismissed: true,
52
content: null,
53
isVisible: false,
54
title: null,
55
};
56
case 'SHOW':
57
return {
58
+ canBeDismissed: action.canBeDismissed !== false,
59
content: action.content,
60
isVisible: true,
61
title: action.title || null,
@@ -67,6 +71,7 @@ type Props = {|
71
72
function ModalDialogContextController({ children }: Props) {
73
const [state, dispatch] = useReducer<State, Action>(dialogReducer, {
74
+ canBeDismissed: true,
75
content: null,
76
isVisible: false,
77
title: null,
@@ -74,6 +79,7 @@ function ModalDialogContextController({ children }: Props) {
79
80
const value = useMemo<ModalDialogContextType>(
81
() => ({
82
+ canBeDismissed: state.canBeDismissed,
83
content: state.content,
84
isVisible: state.isVisible,
85
title: state.title,
@@ -95,10 +101,14 @@ function ModalDialog(_: {||}) {
101
}
102
103
function ModalDialogImpl(_: {||}) {
98
- const { content, dispatch, title } = useContext(ModalDialogContext);
99
- const dismissModal = useCallback(() => dispatch({ type: 'HIDE' }), [
100
- dispatch,
101
- ]);
104
+ const { canBeDismissed, content, dispatch, title } = useContext(
105
+ ModalDialogContext
106
+ );
107
+ const dismissModal = useCallback(() => {
108
+ if (canBeDismissed) {
109
+ dispatch({ type: 'HIDE' });
110
+ }
111
+ }, [canBeDismissed, dispatch]);
112
const modalRef = useRef<HTMLDivElement | null>(null);
113
114
useModalDismissSignal(modalRef, dismissModal);
@@ -108,11 +118,13 @@ function ModalDialogImpl(_: {||}) {
118
<div className={styles.Dialog} ref={modalRef}>
119
{title !== null && <div className={styles.Title}>{title}</div>}
120
{content}
111
- <div className={styles.Buttons}>
112
- <Button autoFocus onClick={dismissModal}>
113
- Okay
114
- </Button>
115
- </div>
121
+ {canBeDismissed && (
122
+ <div className={styles.Buttons}>
123
+ <Button autoFocus onClick={dismissModal}>
124
+ Okay
125
+ </Button>
126
+ </div>
127
+ )}
128
</div>
129
</div>
130
);
src/devtools/views/WarnIfLegacyBackendDetected.css
new
+6
@@ -0,0 +1,6 @@
1
+.Command {
2
+ background-color: var(--color-dimmest);
3
+ padding: 0.25rem 0.5rem;
4
+ display: block;
5
+ border-radius: 0.125rem;
6
+}
src/devtools/views/WarnIfLegacyBackendDetected.js
new
+60
@@ -0,0 +1,60 @@
1
+// @flow
2
+
3
+import React, { Fragment, useContext, useEffect } from 'react';
4
+import { BridgeContext } from './context';
5
+import { ModalDialogContext } from './ModalDialog';
6
+
7
+import styles from './WarnIfLegacyBackendDetected.css';
8
+
9
+export default function WarnIfLegacyBackendDetected(_: {||}) {
10
+ const bridge = useContext(BridgeContext);
11
+ const { dispatch } = useContext(ModalDialogContext);
12
+
13
+ // Detect pairing with legacy v3 backend.
14
+ // We do this by listening to a message that it broadcasts but the v4 backend doesn't.
15
+ // In this case the frontend should show upgrade instructions.
16
+ useEffect(() => {
17
+ // Wall.listen returns a cleanup function
18
+ let unlisten = bridge.wall.listen(event => {
19
+ switch (event.type) {
20
+ case 'call':
21
+ case 'event':
22
+ case 'many-events':
23
+ // Any of these types indicate the v3 backend.
24
+ dispatch({
25
+ canBeDismissed: false,
26
+ type: 'SHOW',
27
+ title:
28
+ 'React DevTools v4 is incompatible with this version of React',
29
+ content: <InvalidBackendDetected />,
30
+ });
31
+
32
+ if (typeof unlisten === 'function') {
33
+ unlisten();
34
+ unlisten = null;
35
+ }
36
+ break;
37
+ default:
38
+ break;
39
+ }
40
+ });
41
+
42
+ return () => {
43
+ if (typeof unlisten === 'function') {
44
+ unlisten();
45
+ unlisten = null;
46
+ }
47
+ };
48
+ }, [bridge, dispatch]);
49
+
50
+ return null;
51
+}
52
+
53
+function InvalidBackendDetected(_: {||}) {
54
+ return (
55
+ <Fragment>
56
+ <p>Either upgrade React or install React DevTools v3:</p>
57
+ <code className={styles.Command}>npm install -d react-devtools@^3</code>
58
+ </Fragment>
59
+ );
60
+}