Detect React Native v3 backend and show warning

This commit is contained in:
Brian Vaughn
2019-07-18 16:24:22 -07:00
parent 9fb753b5cd
commit 249a2e043d
7 changed files with 99 additions and 9 deletions

View File

@@ -86,6 +86,7 @@ function reload() {
bridge: ((bridge: any): Bridge),
showTabBar: true,
store: ((store: any): Store),
warnIfLegacyBackendDetected: true,
viewElementSourceFunction,
viewElementSourceRequiresFileLocation: true,
})

View File

@@ -77,6 +77,7 @@ inject('dist/app.js', () => {
browserTheme: 'light',
showTabBar: true,
store,
warnIfLegacyBackendDetected: true,
})
);
batch.then(() => {

View File

@@ -128,6 +128,12 @@ export default class Bridge extends EventEmitter<{|
}) || null;
}
// Listening directly to the wall isn't advised.
// It can be used to listen for legacy (v3) messages (since they use a different format).
get wall(): Wall {
return this._wall;
}
send(event: string, payload: any, transferable?: Array<any>) {
if (this._isShutdown) {
console.warn(

View File

@@ -18,6 +18,7 @@ import ViewElementSourceContext from './Components/ViewElementSourceContext';
import { ProfilerContextController } from './Profiler/ProfilerContext';
import { ModalDialogContextController } from './ModalDialog';
import ReactLogo from './ReactLogo';
import WarnIfLegacyBackendDetected from './WarnIfLegacyBackendDetected';
import styles from './DevTools.css';
@@ -38,6 +39,7 @@ export type Props = {|
defaultTab?: TabID,
showTabBar?: boolean,
store: Store,
warnIfLegacyBackendDetected?: boolean,
viewElementSourceFunction?: ?ViewElementSource,
viewElementSourceRequiresFileLocation?: boolean,
@@ -80,6 +82,7 @@ export default function DevTools({
settingsPortalContainer,
showTabBar = false,
store,
warnIfLegacyBackendDetected = false,
viewElementSourceFunction,
viewElementSourceRequiresFileLocation = false,
}: Props) {
@@ -143,6 +146,7 @@ export default function DevTools({
</TreeContextController>
</ViewElementSourceContext.Provider>
</SettingsContextController>
{warnIfLegacyBackendDetected && <WarnIfLegacyBackendDetected />}
</ModalDialogContextController>
</StoreContext.Provider>
</BridgeContext.Provider>

View File

@@ -18,6 +18,7 @@ type DIALOG_ACTION_HIDE = {|
|};
type DIALOG_ACTION_SHOW = {|
type: 'SHOW',
canBeDismissed?: boolean,
content: React$Node,
title?: React$Node | null,
|};
@@ -27,6 +28,7 @@ type Action = DIALOG_ACTION_HIDE | DIALOG_ACTION_SHOW;
type Dispatch = (action: Action) => void;
type State = {|
canBeDismissed: boolean,
content: React$Node | null,
isVisible: boolean,
title: React$Node | null,
@@ -46,12 +48,14 @@ function dialogReducer(state, action) {
switch (action.type) {
case 'HIDE':
return {
canBeDismissed: true,
content: null,
isVisible: false,
title: null,
};
case 'SHOW':
return {
canBeDismissed: action.canBeDismissed !== false,
content: action.content,
isVisible: true,
title: action.title || null,
@@ -67,6 +71,7 @@ type Props = {|
function ModalDialogContextController({ children }: Props) {
const [state, dispatch] = useReducer<State, Action>(dialogReducer, {
canBeDismissed: true,
content: null,
isVisible: false,
title: null,
@@ -74,6 +79,7 @@ function ModalDialogContextController({ children }: Props) {
const value = useMemo<ModalDialogContextType>(
() => ({
canBeDismissed: state.canBeDismissed,
content: state.content,
isVisible: state.isVisible,
title: state.title,
@@ -95,10 +101,14 @@ function ModalDialog(_: {||}) {
}
function ModalDialogImpl(_: {||}) {
const { content, dispatch, title } = useContext(ModalDialogContext);
const dismissModal = useCallback(() => dispatch({ type: 'HIDE' }), [
dispatch,
]);
const { canBeDismissed, content, dispatch, title } = useContext(
ModalDialogContext
);
const dismissModal = useCallback(() => {
if (canBeDismissed) {
dispatch({ type: 'HIDE' });
}
}, [canBeDismissed, dispatch]);
const modalRef = useRef<HTMLDivElement | null>(null);
useModalDismissSignal(modalRef, dismissModal);
@@ -108,11 +118,13 @@ function ModalDialogImpl(_: {||}) {
<div className={styles.Dialog} ref={modalRef}>
{title !== null && <div className={styles.Title}>{title}</div>}
{content}
<div className={styles.Buttons}>
<Button autoFocus onClick={dismissModal}>
Okay
</Button>
</div>
{canBeDismissed && (
<div className={styles.Buttons}>
<Button autoFocus onClick={dismissModal}>
Okay
</Button>
</div>
)}
</div>
</div>
);

View File

@@ -0,0 +1,6 @@
.Command {
background-color: var(--color-dimmest);
padding: 0.25rem 0.5rem;
display: block;
border-radius: 0.125rem;
}

View File

@@ -0,0 +1,60 @@
// @flow
import React, { Fragment, useContext, useEffect } from 'react';
import { BridgeContext } from './context';
import { ModalDialogContext } from './ModalDialog';
import styles from './WarnIfLegacyBackendDetected.css';
export default function WarnIfLegacyBackendDetected(_: {||}) {
const bridge = useContext(BridgeContext);
const { dispatch } = useContext(ModalDialogContext);
// Detect pairing with legacy v3 backend.
// We do this by listening to a message that it broadcasts but the v4 backend doesn't.
// In this case the frontend should show upgrade instructions.
useEffect(() => {
// Wall.listen returns a cleanup function
let unlisten = bridge.wall.listen(event => {
switch (event.type) {
case 'call':
case 'event':
case 'many-events':
// Any of these types indicate the v3 backend.
dispatch({
canBeDismissed: false,
type: 'SHOW',
title:
'React DevTools v4 is incompatible with this version of React',
content: <InvalidBackendDetected />,
});
if (typeof unlisten === 'function') {
unlisten();
unlisten = null;
}
break;
default:
break;
}
});
return () => {
if (typeof unlisten === 'function') {
unlisten();
unlisten = null;
}
};
}, [bridge, dispatch]);
return null;
}
function InvalidBackendDetected(_: {||}) {
return (
<Fragment>
<p>Either upgrade React or install React DevTools v3:</p>
<code className={styles.Command}>npm install -d react-devtools@^3</code>
</Fragment>
);
}