[ReactDevTools] show message for unsupported feature

This commit is contained in:
Mengdi Chen
2022-03-29 13:26:59 -04:00
parent 566e046716
commit 1ad075c3dc
8 changed files with 138 additions and 57 deletions
+19 -2
View File
@@ -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 {
+9
View File
@@ -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
@@ -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 (
<div className={styles.ErrorBoundary}>
{children}
<div className={styles.ErrorInfo}>
<div className={styles.HeaderRow}>
<div className={styles.ErrorHeader}>{errorMessage}</div>
</div>
{!!info && <div className={styles.InfoBox}>{info}</div>}
{!!callStack && (
<div className={styles.ErrorStack}>
The error was thrown {callStack.trim()}
</div>
)}
</div>
</div>
);
}
@@ -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<Props, State> {
@@ -63,6 +67,7 @@ export default class ErrorBoundary extends Component<Props, State> {
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<Props, State> {
errorMessage,
hasError: true,
isUnsupportedBridgeOperationError,
isUnsupportedFeatureError,
isTimeout,
isUserError,
};
@@ -118,6 +124,7 @@ export default class ErrorBoundary extends Component<Props, State> {
isUnsupportedBridgeOperationError,
isTimeout,
isUserError,
isUnsupportedFeatureError,
} = this.state;
if (hasError) {
@@ -142,10 +149,34 @@ export default class ErrorBoundary extends Component<Props, State> {
);
} else if (isUserError) {
return (
<UserErrorView
<CaughtErrorView
callStack={callStack}
componentStack={componentStack}
errorMessage={errorMessage}
errorMessage={errorMessage || 'Error occured in inspected element'}
info={
<>
This is likely to be caused by implementation of current
inspected element. Please see your console for logged error.
</>
}
/>
);
} else if (isUnsupportedFeatureError) {
return (
<CaughtErrorView
callStack={callStack}
componentStack={componentStack}
errorMessage={
errorMessage ||
'Current DevTools version does not support a feature used in the inspected element.'
}
info={
<>
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<Props, State> {
dismissError={
canDismissProp || canDismissState ? this._dismissError : null
}
errorMessage={errorMessage}
isUnsupportedBridgeOperationError={
isUnsupportedBridgeOperationError
}>
errorMessage={errorMessage}>
<Suspense fallback={<SearchingGitHubIssues />}>
<SuspendingErrorView
callStack={callStack}
@@ -1,47 +0,0 @@
/**
* 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,
componentStack: string | null,
errorMessage: string | null,
|};
export default function UnsupportedBridgeOperationView({
callStack,
children,
componentStack,
errorMessage,
}: Props) {
return (
<div className={styles.ErrorBoundary}>
{children}
<div className={styles.ErrorInfo}>
<div className={styles.HeaderRow}>
<div className={styles.ErrorHeader}>
{errorMessage || 'Error occured in inspected element'}
</div>
</div>
<div className={styles.InfoBox}>
This is likely to be caused by implementation of current inspected element.
</div>
{!!callStack && (
<div className={styles.ErrorStack}>
The error was thrown {callStack.trim()}
</div>
)}
</div>
</div>
);
}
@@ -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';
}
}
+1 -1
View File
@@ -7,7 +7,7 @@
* @flow
*/
export default class UserError extends Error {
export default class UserError extends Error {
constructor(message: string) {
super(message);
@@ -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);