Skip to content

Commit

Permalink
DevTools shows unsupported renderer version dialog (#16897)
Browse files Browse the repository at this point in the history
* DevTools shows unsupported renderer version dialog

* Optimistic CHANGELOG udpate
  • Loading branch information
Brian Vaughn committed Sep 26, 2019
1 parent 84e83db commit b6606ec
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 10 deletions.
2 changes: 1 addition & 1 deletion packages/react-devtools-extensions/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ function createPanelIfReactLoaded() {
overrideTab,
profilerPortalContainer,
showTabBar: false,
showWelcomeToTheNewDevToolsDialog: true,
warnIfUnsupportedVersionDetected: true,
store,
viewElementSourceFunction,
}),
Expand Down
4 changes: 4 additions & 0 deletions packages/react-devtools-shared/src/backend/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ export default class Agent extends EventEmitter<{|
}
};

onUnsupportedRenderer(rendererID: number) {
this._bridge.send('unsupportedRendererVersion', rendererID);
}

_throttledPersistSelection = throttle((rendererID: number, id: number) => {
// This is throttled, so both renderer and selected ID
// might not be available by the time we read them.
Expand Down
30 changes: 22 additions & 8 deletions packages/react-devtools-shared/src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export function initBackend(
},
),

hook.sub('unsupported-renderer-version', (id: number) => {
agent.onUnsupportedRenderer(id);
}),

hook.sub('operations', agent.onHookOperations),

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

// Inject any not-yet-injected renderers (if we didn't reload-and-profile)
if (!rendererInterface) {
if (rendererInterface == null) {
if (typeof renderer.findFiberByHostInstance === 'function') {
// react-reconciler v16+
rendererInterface = attach(hook, id, renderer, global);
} else {
} else if (renderer.ComponentTree) {
// react-dom v15
rendererInterface = attachLegacy(hook, id, renderer, global);
} else {
// Older react-dom or other unsupported renderer version
}

hook.rendererInterfaces.set(id, rendererInterface);
if (rendererInterface != null) {
hook.rendererInterfaces.set(id, rendererInterface);
}
}

// Notify the DevTools frontend about new renderers.
// This includes any that were attached early (via __REACT_DEVTOOLS_ATTACH__).
hook.emit('renderer-attached', {
id,
renderer,
rendererInterface,
});
if (rendererInterface != null) {
hook.emit('renderer-attached', {
id,
renderer,
rendererInterface,
});
} else {
hook.emit('unsupported-renderer-version', id);
}
};

// Connect renderers that have already injected themselves.
Expand Down
5 changes: 4 additions & 1 deletion packages/react-devtools-shared/src/backend/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ export type ReactRenderer = {
// Enables DevTools to append owners-only component stack to error messages.
getCurrentFiber?: () => Fiber | null,

// <= 15
// Uniquely identifies React DOM v15.
ComponentTree?: any,

// Present for React DOM v12 (possibly earlier) through v15.
Mount?: any,
};

Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools-shared/src/bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type BackendEvents = {|
stopInspectingNative: [boolean],
syncSelectionFromNativeElementsPanel: [],
syncSelectionToNativeElementsPanel: [],
unsupportedRendererVersion: [RendererID],

// React Native style editor plug-in.
isNativeStyleEditorSupported: [
Expand Down
3 changes: 3 additions & 0 deletions packages/react-devtools-shared/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export const PROFILER_EXPORT_VERSION = 4;
export const CHANGE_LOG_URL =
'https://github.com/facebook/react/blob/master/packages/react-devtools/CHANGELOG.md';

export const UNSUPPORTED_VERSION_URL =
'https://reactjs.org/blog/2019/08/15/new-react-devtools.html#how-do-i-get-the-old-version-back';

// HACK
//
// Extracting during build time avoids a temporarily invalid state for the inline target.
Expand Down
17 changes: 17 additions & 0 deletions packages/react-devtools-shared/src/devtools/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default class Store extends EventEmitter<{|
supportsNativeStyleEditor: [],
supportsProfiling: [],
supportsReloadAndProfile: [],
unsupportedRendererVersionDetected: [],
|}> {
_bridge: FrontendBridge;

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

_unsupportedRendererVersionDetected: boolean = false;

// Total number of visible elements (within all roots).
// Used for windowing purposes.
_weightAcrossRoots: number = 0;
Expand Down Expand Up @@ -179,6 +182,10 @@ export default class Store extends EventEmitter<{|
'isNativeStyleEditorSupported',
this.onBridgeNativeStyleEditorSupported,
);
bridge.addListener(
'unsupportedRendererVersion',
this.onBridgeUnsupportedRendererVersion,
);

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

get unsupportedRendererVersionDetected(): boolean {
return this._unsupportedRendererVersionDetected;
}

containsElement(id: number): boolean {
return this._idToElement.get(id) != null;
}
Expand Down Expand Up @@ -1009,4 +1020,10 @@ export default class Store extends EventEmitter<{|

this.emit('supportsReloadAndProfile');
};

onBridgeUnsupportedRendererVersion = () => {
this._unsupportedRendererVersionDetected = true;

this.emit('unsupportedRendererVersionDetected');
};
}
4 changes: 4 additions & 0 deletions packages/react-devtools-shared/src/devtools/views/DevTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ViewElementSourceContext from './Components/ViewElementSourceContext';
import {ProfilerContextController} from './Profiler/ProfilerContext';
import {ModalDialogContextController} from './ModalDialog';
import ReactLogo from './ReactLogo';
import UnsupportedVersionDialog from './UnsupportedVersionDialog';
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';

import styles from './DevTools.css';
Expand Down Expand Up @@ -51,6 +52,7 @@ export type Props = {|
showTabBar?: boolean,
store: Store,
warnIfLegacyBackendDetected?: boolean,
warnIfUnsupportedVersionDetected?: boolean,
viewElementSourceFunction?: ?ViewElementSource,

// This property is used only by the web extension target.
Expand Down Expand Up @@ -92,6 +94,7 @@ export default function DevTools({
showTabBar = false,
store,
warnIfLegacyBackendDetected = false,
warnIfUnsupportedVersionDetected = false,
viewElementSourceFunction,
}: Props) {
const [tab, setTab] = useState(defaultTab);
Expand Down Expand Up @@ -164,6 +167,7 @@ export default function DevTools({
</ViewElementSourceContext.Provider>
</SettingsContextController>
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
{warnIfUnsupportedVersionDetected && <UnsupportedVersionDialog />}
</ModalDialogContextController>
</StoreContext.Provider>
</BridgeContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.Row {
display: flex;
flex-direction: row;
align-items: center;
}

.Column {
display: flex;
flex-direction: column;
align-items: center;
}

.Title {
font-size: var(--font-size-sans-large);
margin-bottom: 0.5rem;
}

.ReleaseNotesLink {
color: var(--color-button-active);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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
*/

import React, {Fragment, useContext, useEffect, useState} from 'react';
import {unstable_batchedUpdates as batchedUpdates} from 'react-dom';
import {ModalDialogContext} from './ModalDialog';
import {StoreContext} from './context';
import {UNSUPPORTED_VERSION_URL} from 'react-devtools-shared/src/constants';

import styles from './UnsupportedVersionDialog.css';

type DAILOG_STATE = 'dialog-not-shown' | 'show-dialog' | 'dialog-shown';

export default function UnsupportedVersionDialog(_: {||}) {
const {dispatch} = useContext(ModalDialogContext);
const store = useContext(StoreContext);
const [state, setState] = useState<DAILOG_STATE>('dialog-not-shown');

useEffect(
() => {
if (state === 'dialog-not-shown') {
const showDialog = () => {
batchedUpdates(() => {
setState('show-dialog');
dispatch({
canBeDismissed: true,
type: 'SHOW',
content: <DialogContent />,
});
});
};

if (store.unsupportedRendererVersionDetected) {
showDialog();
} else {
store.addListener('unsupportedRendererVersionDetected', showDialog);
return () => {
store.removeListener(
'unsupportedRendererVersionDetected',
showDialog,
);
};
}
}
},
[state, store],
);

return null;
}

function DialogContent(_: {||}) {
return (
<Fragment>
<div className={styles.Row}>
<div>
<div className={styles.Title}>Unsupported React version detected</div>
<p>
This version of React DevTools supports React DOM v15+ and React
Native v61+.
</p>
<p>
In order to use DevTools with an older version of React, you'll need
to{' '}
<a
className={styles.ReleaseNotesLink}
target="_blank"
rel="noopener noreferrer"
href={UNSUPPORTED_VERSION_URL}>
install an older version of the extension
</a>.
</p>
</div>
</div>
</Fragment>
);
}
1 change: 1 addition & 0 deletions packages/react-devtools-shell/src/devtools.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ inject('dist/app.js', () => {
browserTheme: 'light',
showTabBar: true,
warnIfLegacyBackendDetected: true,
warnIfUnsupportedVersionDetected: true,
}),
);
},
Expand Down
1 change: 1 addition & 0 deletions packages/react-devtools/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* 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))
* 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))
* 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))
* Show unsupported version dialog with downgrade instructions. ([bvaughn](https://github.com/bvaughn) in [#16897](https://github.com/facebook/react/pull/16897))
</details>

## 4.1.0 (September 19, 2019)
Expand Down

0 comments on commit b6606ec

Please sign in to comment.