From 1ad075c3dc01a1bfac60d16bf5a480fe22f58e62 Mon Sep 17 00:00:00 2001 From: Mengdi Chen Date: Tue, 29 Mar 2022 10:37:38 -0400 Subject: [PATCH] [ReactDevTools] show message for unsupported feature --- .../src/backend/renderer.js | 21 ++++++++- .../src/backend/types.js | 9 ++++ .../views/ErrorBoundary/CaughtErrorView.js | 44 +++++++++++++++++ .../views/ErrorBoundary/ErrorBoundary.js | 42 ++++++++++++++--- .../views/ErrorBoundary/UserErrorView.js | 47 ------------------- .../src/errors/UnsupportedFeatureError.js | 21 +++++++++ .../src/errors/UserError.js | 2 +- .../src/inspectedElementMutableSource.js | 9 ++++ 8 files changed, 138 insertions(+), 57 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/ErrorBoundary/CaughtErrorView.js delete mode 100644 packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UserErrorView.js create mode 100644 packages/react-devtools-shared/src/errors/UnsupportedFeatureError.js diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 10aa4a0dcb..6fd24e689f 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -60,7 +60,10 @@ import { TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS, TREE_OPERATION_UPDATE_TREE_BASE_DURATION, } from '../constants'; -import {inspectHooksOfFiber, ErrorsNames as DebugToolsErrors} from 'react-debug-tools'; +import { + inspectHooksOfFiber, + ErrorsNames as DebugToolsErrors, +} from 'react-debug-tools'; import { patch as patchConsole, registerRenderer as registerRendererWithConsole, @@ -3619,9 +3622,13 @@ export function attach( if (error.name === DebugToolsErrors.RENDER_FUNCTION_ERROR) { let message = 'Error rendering inspected element.'; let stack; + // Log error & cause for user to debug console.error(message + '\n\n', error); if (error.cause != null) { - console.error('Original error causing above error: \n\n', error.cause); + console.error( + 'Original error causing above error: \n\n', + error.cause, + ); if (error.cause instanceof Error) { message = error.cause.message || message; stack = error.cause.stack; @@ -3637,6 +3644,16 @@ export function attach( }; } + if (error.name === DebugToolsErrors.UNSUPPORTTED_FEATURE_ERROR) { + return { + type: 'unsupported-feature', + id, + responseID: requestID, + message: 'Unsupported feature: ' + error.message, + }; + } + + // Log Uncaught Error console.error('Error inspecting element.\n\n', error); return { diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 11019d3f0e..4a263baa52 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -282,6 +282,7 @@ export type InspectedElement = {| export const InspectElementErrorType = 'error'; export const InspectElementUserErrorType = 'user-error'; +export const InspectElementUnsupportedFeatureErrorType = 'unsupported-feature'; export const InspectElementFullDataType = 'full-data'; export const InspectElementNoChangeType = 'no-change'; export const InspectElementNotFoundType = 'not-found'; @@ -302,6 +303,13 @@ export type InspectElementUserError = {| stack: ?string, |}; +export type InspectElementUnsupportedFeatureError = {| + id: number, + responseID: number, + type: 'unsupported-feature', + message: string, +|}; + export type InspectElementFullData = {| id: number, responseID: number, @@ -332,6 +340,7 @@ export type InspectElementNotFound = {| export type InspectedElementPayload = | InspectElementError | InspectElementUserError + | InspectElementUnsupportedFeatureError | InspectElementFullData | InspectElementHydratedPath | InspectElementNoChange diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/CaughtErrorView.js b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/CaughtErrorView.js new file mode 100644 index 0000000000..c4511ba031 --- /dev/null +++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/CaughtErrorView.js @@ -0,0 +1,44 @@ +/** + * 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 * as React from 'react'; +import styles from './shared.css'; + +type Props = {| + callStack: string | null, + children: React$Node, + info: React$Node | null, + componentStack: string | null, + errorMessage: string, +|}; + +export default function CaughtErrorView({ + callStack, + children, + info, + componentStack, + errorMessage, +}: Props) { + return ( +
+ {children} +
+
+
{errorMessage}
+
+ {!!info &&
{info}
} + {!!callStack && ( +
+ The error was thrown {callStack.trim()} +
+ )} +
+
+ ); +} diff --git a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js index c770ce1b43..694e0ca205 100644 --- a/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js +++ b/packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js @@ -15,11 +15,13 @@ import ErrorView from './ErrorView'; import SearchingGitHubIssues from './SearchingGitHubIssues'; import SuspendingErrorView from './SuspendingErrorView'; import TimeoutView from './TimeoutView'; -import UserErrorView from './UserErrorView'; +import CaughtErrorView from './CaughtErrorView'; import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError'; import TimeoutError from 'react-devtools-shared/src/errors/TimeoutError'; import UserError from 'react-devtools-shared/src/errors/UserError'; +import UnsupportedFeatureError from 'react-devtools-shared/src/errors/UnsupportedFeatureError'; import {logEvent} from 'react-devtools-shared/src/Logger'; +import {boolean} from 'yargs'; type Props = {| children: React$Node, @@ -37,6 +39,7 @@ type State = {| isUnsupportedBridgeOperationError: boolean, isTimeout: boolean, isUserError: boolean, + isUnsupportedFeatureError: boolean, |}; const InitialState: State = { @@ -48,6 +51,7 @@ const InitialState: State = { isUnsupportedBridgeOperationError: false, isTimeout: false, isUserError: false, + isUnsupportedFeatureError: false, }; export default class ErrorBoundary extends Component { @@ -63,6 +67,7 @@ export default class ErrorBoundary extends Component { const isTimeout = error instanceof TimeoutError; const isUserError = error instanceof UserError; + const isUnsupportedFeatureError = error instanceof UnsupportedFeatureError; const isUnsupportedBridgeOperationError = error instanceof UnsupportedBridgeOperationError; @@ -81,6 +86,7 @@ export default class ErrorBoundary extends Component { errorMessage, hasError: true, isUnsupportedBridgeOperationError, + isUnsupportedFeatureError, isTimeout, isUserError, }; @@ -118,6 +124,7 @@ export default class ErrorBoundary extends Component { isUnsupportedBridgeOperationError, isTimeout, isUserError, + isUnsupportedFeatureError, } = this.state; if (hasError) { @@ -142,10 +149,34 @@ export default class ErrorBoundary extends Component { ); } else if (isUserError) { return ( - + This is likely to be caused by implementation of current + inspected element. Please see your console for logged error. + + } + /> + ); + } else if (isUnsupportedFeatureError) { + return ( + + React DevTools is unable to handle a feature you are using in + this component (e.g. a new React build-in Hook). Please upgrade + to the latest version. + + } /> ); } else { @@ -156,10 +187,7 @@ export default class ErrorBoundary extends Component { dismissError={ canDismissProp || canDismissState ? this._dismissError : null } - errorMessage={errorMessage} - isUnsupportedBridgeOperationError={ - isUnsupportedBridgeOperationError - }> + errorMessage={errorMessage}> }> - {children} -
-
-
- {errorMessage || 'Error occured in inspected element'} -
-
-
- This is likely to be caused by implementation of current inspected element. -
- {!!callStack && ( -
- The error was thrown {callStack.trim()} -
- )} -
- - ); - } - \ No newline at end of file diff --git a/packages/react-devtools-shared/src/errors/UnsupportedFeatureError.js b/packages/react-devtools-shared/src/errors/UnsupportedFeatureError.js new file mode 100644 index 0000000000..0f98b0b376 --- /dev/null +++ b/packages/react-devtools-shared/src/errors/UnsupportedFeatureError.js @@ -0,0 +1,21 @@ +/** + * 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 + */ + +export default class UnsupportedFeatureError extends Error { + constructor(message: string) { + super(message); + + // Maintains proper stack trace for where our error was thrown (only available on V8) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, UnsupportedFeatureError); + } + + this.name = 'UnsupportedFeatureError'; + } +} diff --git a/packages/react-devtools-shared/src/errors/UserError.js b/packages/react-devtools-shared/src/errors/UserError.js index 752dbf9e88..81a9ee86e5 100644 --- a/packages/react-devtools-shared/src/errors/UserError.js +++ b/packages/react-devtools-shared/src/errors/UserError.js @@ -7,7 +7,7 @@ * @flow */ - export default class UserError extends Error { +export default class UserError extends Error { constructor(message: string) { super(message); diff --git a/packages/react-devtools-shared/src/inspectedElementMutableSource.js b/packages/react-devtools-shared/src/inspectedElementMutableSource.js index 329f174f15..4396d27d63 100644 --- a/packages/react-devtools-shared/src/inspectedElementMutableSource.js +++ b/packages/react-devtools-shared/src/inspectedElementMutableSource.js @@ -21,6 +21,7 @@ import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; import type { InspectElementError, InspectElementUserError, + InspectElementUnsupportedFeatureError, InspectElementFullData, InspectElementHydratedPath, } from 'react-devtools-shared/src/backend/types'; @@ -30,6 +31,7 @@ import type { InspectedElementResponseType, } from 'react-devtools-shared/src/devtools/views/Components/types'; import UserError from 'react-devtools-shared/src/errors/UserError'; +import UnsupportedFeatureError from 'react-devtools-shared/src/errors/UnsupportedFeatureError'; // Maps element ID to inspected data. // We use an LRU for this rather than a WeakMap because of how the "no-change" optimization works. @@ -101,6 +103,13 @@ export function inspectElement({ throw error; } + case 'unsupported-faeture': { + const {message} = (data: InspectElementUnsupportedFeatureError); + // Trying to keep useful information from user's side. + const error = new UnsupportedFeatureError(message); + throw error; + } + case 'no-change': // This is a no-op for the purposes of our cache. inspectedElement = inspectedElementCache.get(id);