diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js index 81c0a3a758212..919fbb059f76b 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js @@ -2145,7 +2145,7 @@ describe('InspectedElement', () => { expect(value).toBe(null); const error = errorBoundaryInstance.state.error; - expect(error.message).toBe('Error rendering inspected component'); + expect(error.message).toBe('Expected'); expect(error.stack).toContain('inspectHooksOfFiber'); }); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index c161902df711c..5e09a2d0f8043 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -3607,10 +3607,58 @@ export function attach( try { mostRecentlyInspectedElement = inspectElementRaw(id); } catch (error) { + // the error name is synced with ReactDebugHooks + if (error.name === 'ReactDebugToolsRenderError') { + 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) { + const fiber = findCurrentFiberUsingSlowPathById(id); + const componentName = + fiber != null ? getDisplayNameForFiber(fiber) : null; + console.error( + 'React DevTools encountered an error while trying to inspect hooks. ' + + 'This is most likely caused by an error in current inspected component' + + (componentName != null ? `: "${componentName}".` : '.') + + '\nThe error thrown in the component is: \n\n', + error.cause, + ); + if (error.cause instanceof Error) { + message = error.cause.message || message; + stack = error.cause.stack; + } + } + + return { + type: 'error', + errorType: 'user', + id, + responseID: requestID, + message, + stack, + }; + } + + // the error name is synced with ReactDebugHooks + if (error.name === 'ReactDebugToolsUnsupportedHookError') { + return { + type: 'error', + errorType: 'unknown-hook', + id, + responseID: requestID, + message: + 'Unsupported hook in the react-debug-tools package: ' + + error.message, + }; + } + + // Log Uncaught Error console.error('Error inspecting element.\n\n', error); return { type: 'error', + errorType: 'uncaught', id, responseID: requestID, message: error.message, diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index 4d975dbfec0d5..92ad42c3010cf 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -289,8 +289,9 @@ export type InspectElementError = {| id: number, responseID: number, type: 'error', + errorType: 'user' | 'unknown-hook' | 'uncaught', message: string, - stack: string, + stack?: string, |}; export type InspectElementFullData = {| diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index 3849899b7df02..adf0c5e8b0911 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -10,7 +10,7 @@ import {hydrate, fillInPath} from 'react-devtools-shared/src/hydration'; import {separateDisplayNameAndHOCs} from 'react-devtools-shared/src/utils'; import Store from 'react-devtools-shared/src/devtools/store'; -import TimeoutError from 'react-devtools-shared/src/TimeoutError'; +import TimeoutError from 'react-devtools-shared/src/errors/TimeoutError'; import type { InspectedElement as InspectedElementBackend, 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 0000000000000..c4511ba0312bd --- /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 ( +