Skip to content

Commit 249a2e0

Browse files
author
Brian Vaughn
committed
Detect React Native v3 backend and show warning
1 parent 9fb753b commit 249a2e0

File tree

7 files changed

+99
-9
lines changed

7 files changed

+99
-9
lines changed

packages/react-devtools-core/src/standalone.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ function reload() {
8686
bridge: ((bridge: any): Bridge),
8787
showTabBar: true,
8888
store: ((store: any): Store),
89+
warnIfLegacyBackendDetected: true,
8990
viewElementSourceFunction,
9091
viewElementSourceRequiresFileLocation: true,
9192
})

shells/dev/src/devtools.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ inject('dist/app.js', () => {
7777
browserTheme: 'light',
7878
showTabBar: true,
7979
store,
80+
warnIfLegacyBackendDetected: true,
8081
})
8182
);
8283
batch.then(() => {

src/bridge.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ export default class Bridge extends EventEmitter<{|
128128
}) || null;
129129
}
130130

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+
131137
send(event: string, payload: any, transferable?: Array<any>) {
132138
if (this._isShutdown) {
133139
console.warn(

src/devtools/views/DevTools.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import ViewElementSourceContext from './Components/ViewElementSourceContext';
1818
import { ProfilerContextController } from './Profiler/ProfilerContext';
1919
import { ModalDialogContextController } from './ModalDialog';
2020
import ReactLogo from './ReactLogo';
21+
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
2122

2223
import styles from './DevTools.css';
2324

@@ -38,6 +39,7 @@ export type Props = {|
3839
defaultTab?: TabID,
3940
showTabBar?: boolean,
4041
store: Store,
42+
warnIfLegacyBackendDetected?: boolean,
4143
viewElementSourceFunction?: ?ViewElementSource,
4244
viewElementSourceRequiresFileLocation?: boolean,
4345

@@ -80,6 +82,7 @@ export default function DevTools({
8082
settingsPortalContainer,
8183
showTabBar = false,
8284
store,
85+
warnIfLegacyBackendDetected = false,
8386
viewElementSourceFunction,
8487
viewElementSourceRequiresFileLocation = false,
8588
}: Props) {
@@ -143,6 +146,7 @@ export default function DevTools({
143146
</TreeContextController>
144147
</ViewElementSourceContext.Provider>
145148
</SettingsContextController>
149+
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
146150
</ModalDialogContextController>
147151
</StoreContext.Provider>
148152
</BridgeContext.Provider>

src/devtools/views/ModalDialog.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type DIALOG_ACTION_HIDE = {|
1818
|};
1919
type DIALOG_ACTION_SHOW = {|
2020
type: 'SHOW',
21+
canBeDismissed?: boolean,
2122
content: React$Node,
2223
title?: React$Node | null,
2324
|};
@@ -27,6 +28,7 @@ type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
2728
type Dispatch = (action: Action) => void;
2829

2930
type State = {|
31+
canBeDismissed: boolean,
3032
content: React$Node | null,
3133
isVisible: boolean,
3234
title: React$Node | null,
@@ -46,12 +48,14 @@ function dialogReducer(state, action) {
4648
switch (action.type) {
4749
case 'HIDE':
4850
return {
51+
canBeDismissed: true,
4952
content: null,
5053
isVisible: false,
5154
title: null,
5255
};
5356
case 'SHOW':
5457
return {
58+
canBeDismissed: action.canBeDismissed !== false,
5559
content: action.content,
5660
isVisible: true,
5761
title: action.title || null,
@@ -67,13 +71,15 @@ type Props = {|
6771

6872
function ModalDialogContextController({ children }: Props) {
6973
const [state, dispatch] = useReducer<State, Action>(dialogReducer, {
74+
canBeDismissed: true,
7075
content: null,
7176
isVisible: false,
7277
title: null,
7378
});
7479

7580
const value = useMemo<ModalDialogContextType>(
7681
() => ({
82+
canBeDismissed: state.canBeDismissed,
7783
content: state.content,
7884
isVisible: state.isVisible,
7985
title: state.title,
@@ -95,10 +101,14 @@ function ModalDialog(_: {||}) {
95101
}
96102

97103
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]);
102112
const modalRef = useRef<HTMLDivElement | null>(null);
103113

104114
useModalDismissSignal(modalRef, dismissModal);
@@ -108,11 +118,13 @@ function ModalDialogImpl(_: {||}) {
108118
<div className={styles.Dialog} ref={modalRef}>
109119
{title !== null && <div className={styles.Title}>{title}</div>}
110120
{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+
)}
116128
</div>
117129
</div>
118130
);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)