@samitouri / QOS-React-2 / commits / e62a8d7545

Store throws a specific Error type (UnsupportedBridgeOperationError) (#24147)

When this Error type is detected, DevTools shows a custom error overlay with upgrade/downgrade instructions.

Brian Vaughn committed Mar 23, 2022 at 14:04 UTC e62a8d754548a490c2a3bcff3b420e5eedaf11c0
5 files changed +104 -2
packages/react-devtools-shared/src/UnsupportedBridgeOperationError.js new
+21
@@ -0,0 +1,21 @@
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 +export default class UnsupportedBridgeOperationError extends Error {
11 + constructor(message: string) {
12 + super(message);
13 +
14 + // Maintains proper stack trace for where our error was thrown (only available on V8)
15 + if (Error.captureStackTrace) {
16 + Error.captureStackTrace(this, UnsupportedBridgeOperationError);
17 + }
18 +
19 + this.name = 'UnsupportedBridgeOperationError';
20 + }
21 +}
packages/react-devtools-shared/src/devtools/store.js
+4 -1
@@ -44,6 +44,7 @@ import type {
44 FrontendBridge,
45 BridgeProtocol,
46 } from 'react-devtools-shared/src/bridge';
47 +import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
48
49 const debug = (methodName, ...args) => {
50 if (__DEBUG__) {
@@ -1252,7 +1253,9 @@ export default class Store extends EventEmitter<{|
1253 break;
1254 default:
1255 this._throwAndEmitError(
1255 - Error(`Unsupported Bridge operation "${operation}"`),
1256 + new UnsupportedBridgeOperationError(
1257 + `Unsupported Bridge operation "${operation}"`,
1258 + ),
1259 );
1260 }
1261 }
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
+20 -1
@@ -10,10 +10,12 @@
10 import * as React from 'react';
11 import {Component, Suspense} from 'react';
12 import Store from 'react-devtools-shared/src/devtools/store';
13 +import UnsupportedBridgeOperationView from './UnsupportedBridgeOperationView';
14 import ErrorView from './ErrorView';
15 import SearchingGitHubIssues from './SearchingGitHubIssues';
16 import SuspendingErrorView from './SuspendingErrorView';
17 import TimeoutView from './TimeoutView';
18 +import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
19 import TimeoutError from 'react-devtools-shared/src/TimeoutError';
20 import {logEvent} from 'react-devtools-shared/src/Logger';
21
@@ -30,6 +32,7 @@ type State = {|
32 componentStack: string | null,
33 errorMessage: string | null,
34 hasError: boolean,
35 + isUnsupportedBridgeOperationError: boolean,
36 isTimeout: boolean,
37 |};
38
@@ -39,6 +42,7 @@ const InitialState: State = {
42 componentStack: null,
43 errorMessage: null,
44 hasError: false,
45 + isUnsupportedBridgeOperationError: false,
46 isTimeout: false,
47 };
48
@@ -54,6 +58,8 @@ export default class ErrorBoundary extends Component<Props, State> {
58 : null;
59
60 const isTimeout = error instanceof TimeoutError;
61 + const isUnsupportedBridgeOperationError =
62 + error instanceof UnsupportedBridgeOperationError;
63
64 const callStack =
65 typeof error === 'object' &&
@@ -69,6 +75,7 @@ export default class ErrorBoundary extends Component<Props, State> {
75 callStack,
76 errorMessage,
77 hasError: true,
78 + isUnsupportedBridgeOperationError,
79 isTimeout,
80 };
81 }
@@ -102,6 +109,7 @@ export default class ErrorBoundary extends Component<Props, State> {
109 componentStack,
110 errorMessage,
111 hasError,
112 + isUnsupportedBridgeOperationError,
113 isTimeout,
114 } = this.state;
115
@@ -117,6 +125,14 @@ export default class ErrorBoundary extends Component<Props, State> {
125 errorMessage={errorMessage}
126 />
127 );
128 + } else if (isUnsupportedBridgeOperationError) {
129 + return (
130 + <UnsupportedBridgeOperationView
131 + callStack={callStack}
132 + componentStack={componentStack}
133 + errorMessage={errorMessage}
134 + />
135 + );
136 } else {
137 return (
138 <ErrorView
@@ -125,7 +141,10 @@ export default class ErrorBoundary extends Component<Props, State> {
141 dismissError={
142 canDismissProp || canDismissState ? this._dismissError : null
143 }
128 - errorMessage={errorMessage}>
144 + errorMessage={errorMessage}
145 + isUnsupportedBridgeOperationError={
146 + isUnsupportedBridgeOperationError
147 + }>
148 <Suspense fallback={<SearchingGitHubIssues />}>
149 <SuspendingErrorView
150 callStack={callStack}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UnsupportedBridgeOperationView.js new
+50
@@ -0,0 +1,50 @@
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 * as React from 'react';
11 +import styles from './shared.css';
12 +
13 +type Props = {|
14 + callStack: string | null,
15 + children: React$Node,
16 + componentStack: string | null,
17 + errorMessage: string | null,
18 +|};
19 +
20 +export default function UnsupportedBridgeOperationView({
21 + callStack,
22 + children,
23 + componentStack,
24 + errorMessage,
25 +}: Props) {
26 + return (
27 + <div className={styles.ErrorBoundary}>
28 + {children}
29 + <div className={styles.ErrorInfo}>
30 + <div className={styles.HeaderRow}>
31 + <div className={styles.ErrorHeader}>
32 + {errorMessage || 'Bridge protocol mismatch'}
33 + </div>
34 + </div>
35 + <div className={styles.InfoBox}>
36 + An incompatible version of <code>react-devtools-core</code> has been
37 + embedded in a renderer like React Native. To fix this, update the{' '}
38 + <code>react-devtools-core</code> package within the React Native
39 + application, or downgrade the <code>react-devtools</code> package you
40 + use to open the DevTools UI.
41 + </div>
42 + {!!callStack && (
43 + <div className={styles.ErrorStack}>
44 + The error was thrown {callStack.trim()}
45 + </div>
46 + )}
47 + </div>
48 + </div>
49 + );
50 +}
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/shared.css
+9
@@ -121,4 +121,13 @@
121
122 .CloseButtonIcon {
123 margin-left: 0.25rem;
124 +}
125 +
126 +.InfoBox {
127 + margin-top: 0.5rem;
128 + background: var(--color-console-warning-background);
129 + border: 1px solid var(--color-console-warning-border);
130 + padding: 0.25rem 0.5rem;
131 + border-radius: 0.5rem;
132 + color: var(--color-console-warning-text);
133 }
\ No newline at end of file