mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[ReactDevTools] show message for unsupported feature
This commit is contained in:
+19
-2
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
+44
@@ -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>
|
||||
);
|
||||
}
|
||||
+35
-7
@@ -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}
|
||||
|
||||
-47
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user