Skip to content

Commit b6606ec

Browse files
author
Brian Vaughn
authored
DevTools shows unsupported renderer version dialog (#16897)
* DevTools shows unsupported renderer version dialog * Optimistic CHANGELOG udpate
1 parent 84e83db commit b6606ec

File tree

12 files changed

+161
-10
lines changed

12 files changed

+161
-10
lines changed

packages/react-devtools-extensions/src/main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ function createPanelIfReactLoaded() {
161161
overrideTab,
162162
profilerPortalContainer,
163163
showTabBar: false,
164-
showWelcomeToTheNewDevToolsDialog: true,
164+
warnIfUnsupportedVersionDetected: true,
165165
store,
166166
viewElementSourceFunction,
167167
}),

packages/react-devtools-shared/src/backend/agent.js

+4
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,10 @@ export default class Agent extends EventEmitter<{|
474474
}
475475
};
476476

477+
onUnsupportedRenderer(rendererID: number) {
478+
this._bridge.send('unsupportedRendererVersion', rendererID);
479+
}
480+
477481
_throttledPersistSelection = throttle((rendererID: number, id: number) => {
478482
// This is throttled, so both renderer and selected ID
479483
// might not be available by the time we read them.

packages/react-devtools-shared/src/backend/index.js

+22-8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export function initBackend(
3939
},
4040
),
4141

42+
hook.sub('unsupported-renderer-version', (id: number) => {
43+
agent.onUnsupportedRenderer(id);
44+
}),
45+
4246
hook.sub('operations', agent.onHookOperations),
4347

4448
// TODO Add additional subscriptions required for profiling mode
@@ -48,23 +52,33 @@ export function initBackend(
4852
let rendererInterface = hook.rendererInterfaces.get(id);
4953

5054
// Inject any not-yet-injected renderers (if we didn't reload-and-profile)
51-
if (!rendererInterface) {
55+
if (rendererInterface == null) {
5256
if (typeof renderer.findFiberByHostInstance === 'function') {
57+
// react-reconciler v16+
5358
rendererInterface = attach(hook, id, renderer, global);
54-
} else {
59+
} else if (renderer.ComponentTree) {
60+
// react-dom v15
5561
rendererInterface = attachLegacy(hook, id, renderer, global);
62+
} else {
63+
// Older react-dom or other unsupported renderer version
5664
}
5765

58-
hook.rendererInterfaces.set(id, rendererInterface);
66+
if (rendererInterface != null) {
67+
hook.rendererInterfaces.set(id, rendererInterface);
68+
}
5969
}
6070

6171
// Notify the DevTools frontend about new renderers.
6272
// This includes any that were attached early (via __REACT_DEVTOOLS_ATTACH__).
63-
hook.emit('renderer-attached', {
64-
id,
65-
renderer,
66-
rendererInterface,
67-
});
73+
if (rendererInterface != null) {
74+
hook.emit('renderer-attached', {
75+
id,
76+
renderer,
77+
rendererInterface,
78+
});
79+
} else {
80+
hook.emit('unsupported-renderer-version', id);
81+
}
6882
};
6983

7084
// Connect renderers that have already injected themselves.

packages/react-devtools-shared/src/backend/types.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ export type ReactRenderer = {
8181
// Enables DevTools to append owners-only component stack to error messages.
8282
getCurrentFiber?: () => Fiber | null,
8383

84-
// <= 15
84+
// Uniquely identifies React DOM v15.
85+
ComponentTree?: any,
86+
87+
// Present for React DOM v12 (possibly earlier) through v15.
8588
Mount?: any,
8689
};
8790

packages/react-devtools-shared/src/bridge.js

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type BackendEvents = {|
8383
stopInspectingNative: [boolean],
8484
syncSelectionFromNativeElementsPanel: [],
8585
syncSelectionToNativeElementsPanel: [],
86+
unsupportedRendererVersion: [RendererID],
8687

8788
// React Native style editor plug-in.
8889
isNativeStyleEditorSupported: [

packages/react-devtools-shared/src/constants.js

+3
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export const PROFILER_EXPORT_VERSION = 4;
3535
export const CHANGE_LOG_URL =
3636
'https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md';
3737

38+
export const UNSUPPORTED_VERSION_URL =
39+
'https://reactjs.org/blog/2019/08/15/new-react-devtools.html#how-do-i-get-the-old-version-back';
40+
3841
// HACK
3942
//
4043
// Extracting during build time avoids a temporarily invalid state for the inline target.

packages/react-devtools-shared/src/devtools/store.js

+17
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default class Store extends EventEmitter<{|
7373
supportsNativeStyleEditor: [],
7474
supportsProfiling: [],
7575
supportsReloadAndProfile: [],
76+
unsupportedRendererVersionDetected: [],
7677
|}> {
7778
_bridge: FrontendBridge;
7879

@@ -125,6 +126,8 @@ export default class Store extends EventEmitter<{|
125126
_supportsProfiling: boolean = false;
126127
_supportsReloadAndProfile: boolean = false;
127128

129+
_unsupportedRendererVersionDetected: boolean = false;
130+
128131
// Total number of visible elements (within all roots).
129132
// Used for windowing purposes.
130133
_weightAcrossRoots: number = 0;
@@ -179,6 +182,10 @@ export default class Store extends EventEmitter<{|
179182
'isNativeStyleEditorSupported',
180183
this.onBridgeNativeStyleEditorSupported,
181184
);
185+
bridge.addListener(
186+
'unsupportedRendererVersion',
187+
this.onBridgeUnsupportedRendererVersion,
188+
);
182189

183190
this._profilerStore = new ProfilerStore(bridge, this, isProfiling);
184191
}
@@ -337,6 +344,10 @@ export default class Store extends EventEmitter<{|
337344
return this._supportsReloadAndProfile && this._isBackendStorageAPISupported;
338345
}
339346

347+
get unsupportedRendererVersionDetected(): boolean {
348+
return this._unsupportedRendererVersionDetected;
349+
}
350+
340351
containsElement(id: number): boolean {
341352
return this._idToElement.get(id) != null;
342353
}
@@ -1009,4 +1020,10 @@ export default class Store extends EventEmitter<{|
10091020

10101021
this.emit('supportsReloadAndProfile');
10111022
};
1023+
1024+
onBridgeUnsupportedRendererVersion = () => {
1025+
this._unsupportedRendererVersionDetected = true;
1026+
1027+
this.emit('unsupportedRendererVersionDetected');
1028+
};
10121029
}

packages/react-devtools-shared/src/devtools/views/DevTools.js

+4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import ViewElementSourceContext from './Components/ViewElementSourceContext';
2424
import {ProfilerContextController} from './Profiler/ProfilerContext';
2525
import {ModalDialogContextController} from './ModalDialog';
2626
import ReactLogo from './ReactLogo';
27+
import UnsupportedVersionDialog from './UnsupportedVersionDialog';
2728
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
2829

2930
import styles from './DevTools.css';
@@ -51,6 +52,7 @@ export type Props = {|
5152
showTabBar?: boolean,
5253
store: Store,
5354
warnIfLegacyBackendDetected?: boolean,
55+
warnIfUnsupportedVersionDetected?: boolean,
5456
viewElementSourceFunction?: ?ViewElementSource,
5557

5658
// This property is used only by the web extension target.
@@ -92,6 +94,7 @@ export default function DevTools({
9294
showTabBar = false,
9395
store,
9496
warnIfLegacyBackendDetected = false,
97+
warnIfUnsupportedVersionDetected = false,
9598
viewElementSourceFunction,
9699
}: Props) {
97100
const [tab, setTab] = useState(defaultTab);
@@ -164,6 +167,7 @@ export default function DevTools({
164167
</ViewElementSourceContext.Provider>
165168
</SettingsContextController>
166169
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
170+
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
167171
</ModalDialogContextController>
168172
</StoreContext.Provider>
169173
</BridgeContext.Provider>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
.Row {
2+
display: flex;
3+
flex-direction: row;
4+
align-items: center;
5+
}
6+
7+
.Column {
8+
display: flex;
9+
flex-direction: column;
10+
align-items: center;
11+
}
12+
13+
.Title {
14+
font-size: var(--font-size-sans-large);
15+
margin-bottom: 0.5rem;
16+
}
17+
18+
.ReleaseNotesLink {
19+
color: var(--color-button-active);
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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+
*/
9+
10+
import React, {Fragment, useContext, useEffect, useState} from 'react';
11+
import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
12+
import {ModalDialogContext} from './ModalDialog';
13+
import {StoreContext} from './context';
14+
import {UNSUPPORTED_VERSION_URL} from 'react-devtools-shared/src/constants';
15+
16+
import styles from './UnsupportedVersionDialog.css';
17+
18+
type DAILOG_STATE = 'dialog-not-shown' | 'show-dialog' | 'dialog-shown';
19+
20+
export default function UnsupportedVersionDialog(_: {||}) {
21+
const {dispatch} = useContext(ModalDialogContext);
22+
const store = useContext(StoreContext);
23+
const [state, setState] = useState<DAILOG_STATE>('dialog-not-shown');
24+
25+
useEffect(
26+
() => {
27+
if (state === 'dialog-not-shown') {
28+
const showDialog = () => {
29+
batchedUpdates(() => {
30+
setState('show-dialog');
31+
dispatch({
32+
canBeDismissed: true,
33+
type: 'SHOW',
34+
content: <DialogContent />,
35+
});
36+
});
37+
};
38+
39+
if (store.unsupportedRendererVersionDetected) {
40+
showDialog();
41+
} else {
42+
store.addListener('unsupportedRendererVersionDetected', showDialog);
43+
return () => {
44+
store.removeListener(
45+
'unsupportedRendererVersionDetected',
46+
showDialog,
47+
);
48+
};
49+
}
50+
}
51+
},
52+
[state, store],
53+
);
54+
55+
return null;
56+
}
57+
58+
function DialogContent(_: {||}) {
59+
return (
60+
<Fragment>
61+
<div className={styles.Row}>
62+
<div>
63+
<div className={styles.Title}>Unsupported React version detected</div>
64+
<p>
65+
This version of React DevTools supports React DOM v15+ and React
66+
Native v61+.
67+
</p>
68+
<p>
69+
In order to use DevTools with an older version of React, you'll need
70+
to{' '}
71+
<a
72+
className={styles.ReleaseNotesLink}
73+
target="_blank"
74+
rel="noopener noreferrer"
75+
href={UNSUPPORTED_VERSION_URL}>
76+
install an older version of the extension
77+
</a>.
78+
</p>
79+
</div>
80+
</div>
81+
</Fragment>
82+
);
83+
}

packages/react-devtools-shell/src/devtools.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ inject('dist/app.js', () => {
5757
browserTheme: 'light',
5858
showTabBar: true,
5959
warnIfLegacyBackendDetected: true,
60+
warnIfUnsupportedVersionDetected: true,
6061
}),
6162
);
6263
},

packages/react-devtools/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* Fixed bug where Components panel was always empty for certain users. ([linshunghuang](https://github.com/linshunghuang) in [#16900](https://github.com/facebook/react/pull/16900))
1313
* Fixed regression in DevTools editable hooks interface that caused primitive values to be shown as `undefined`. ([bvaughn](https://github.com/bvaughn) in [#16867](https://github.com/facebook/react/pull/16867))
1414
* Fixed bug where DevTools showed stale values in props/state/hooks editing interface. ([bvaughn](https://github.com/bvaughn) in [#16878](https://github.com/facebook/react/pull/16878))
15+
* Show unsupported version dialog with downgrade instructions. ([bvaughn](https://github.com/bvaughn) in [#16897](https://github.com/facebook/react/pull/16897))
1516
</details>
1617

1718
## 4.1.0 (September 19, 2019)

0 commit comments

Comments
 (0)