Repository: facebook/react
Pull request: Improve error boundary in inspected elements panel

Show more info about the error as well as the option to report it to GitHub.

The previous error boundary was probably preventing users from reporting certain kinds of issues (like #21528).

### Before
![Error boundary before](https://user-images.githubusercontent.com/29597/118855934-58dda480-b8a4-11eb-98c7-8fee54a9439e.png)

### After
![Error boundary after](https://user-images.githubusercontent.com/29597/118855938-59763b00-b8a4-11eb-9a1c-42fbbb25f9cb.png)


Changed files:
filename: packages/react-devtools-shared/src/devtools/views/Components/InspectedElementErrorBoundary.css
patch: @@ -1,21 +1,3 @@
-.Error {
-  justify-content: center;
-  align-items: center;
-  display: flex;
-      flex-direction: column;
-  height: 100%;
-  font-size: var(--font-size-sans-large);
-    font-weight: bold;
-    text-align: center;
-  background-color: var(--color-error-background);
-  color: var(--color-error-text);
-  border: 1px solid var(--color-error-border);
-  padding: 1rem;
-}
-
-.Message {
-  margin-bottom: 1rem;
-}
-
-.RetryButton {
+.Wrapper {
+  border-left: 1px solid var(--color-border);
 }
\ No newline at end of file

filename: packages/react-devtools-shared/src/devtools/views/Components/InspectedElementErrorBoundary.js
patch: @@ -8,86 +8,19 @@
  */
 
 import * as React from 'react';
-import {Component, useContext} from 'react';
-import {TreeDispatcherContext} from './TreeContext';
-import Button from 'react-devtools-shared/src/devtools/views/Button';
+import ErrorBoundary from '../ErrorBoundary';
 import styles from './InspectedElementErrorBoundary.css';
 
-import type {DispatcherContext} from './InspectedElementErrorBoundary.css';
-
 type WrapperProps = {|
   children: React$Node,
 |};
 
 export default function InspectedElementErrorBoundaryWrapper({
   children,
 }: WrapperProps) {
-  const dispatch = useContext(TreeDispatcherContext);
-
   return (
-    <InspectedElementErrorBoundary children={children} dispatch={dispatch} />
+    <div className={styles.Wrapper}>
+      <ErrorBoundary canDismiss={true}>{children}</ErrorBoundary>
+    </div>
   );
 }
-
-type Props = {|
-  children: React$Node,
-  dispatch: DispatcherContext,
-|};
-
-type State = {|
-  errorMessage: string | null,
-  hasError: boolean,
-|};
-
-const InitialState: State = {
-  errorMessage: null,
-  hasError: false,
-};
-
-class InspectedElementErrorBoundary extends Component<Props, State> {
-  state: State = InitialState;
-
-  static getDerivedStateFromError(error: any) {
-    const errorMessage =
-      typeof error === 'object' &&
-      error !== null &&
-      error.hasOwnProperty('message')
-        ? error.message
-        : error;
-
-    return {
-      errorMessage,
-      hasError: true,
-    };
-  }
-
-  render() {
-    const {children} = this.props;
-    const {errorMessage, hasError} = this.state;
-
-    if (hasError) {
-      return (
-        <div className={styles.Error}>
-          <div className={styles.Message}>{errorMessage || 'Error'}</div>
-          <Button className={styles.RetryButton} onClick={this._retry}>
-            Dismiss
-          </Button>
-        </div>
-      );
-    }
-
-    return children;
-  }
-
-  _retry = () => {
-    const {dispatch} = this.props;
-    dispatch({
-      type: 'SELECT_ELEMENT_BY_ID',
-      payload: null,
-    });
-    this.setState({
-      errorMessage: null,
-      hasError: false,
-    });
-  };
-}

filename: packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
patch: @@ -16,7 +16,8 @@ import SuspendingErrorView from './SuspendingErrorView';
 
 type Props = {|
   children: React$Node,
-  store: Store,
+  canDismiss?: boolean,
+  store?: Store,
 |};
 
 type State = {|
@@ -70,18 +71,24 @@ export default class ErrorBoundary extends Component<Props, State> {
   }
 
   componentDidMount() {
-    this.props.store.addListener('error', this._onStoreError);
+    const {store} = this.props;
+    if (store != null) {
+      store.addListener('error', this._onStoreError);
+    }
   }
 
   componentWillUnmount() {
-    this.props.store.removeListener('error', this._onStoreError);
+    const {store} = this.props;
+    if (store != null) {
+      store.removeListener('error', this._onStoreError);
+    }
   }
 
   render() {
-    const {children} = this.props;
+    const {canDismiss: canDismissProp, children} = this.props;
     const {
       callStack,
-      canDismiss,
+      canDismiss: canDismissState,
       componentStack,
       errorMessage,
       hasError,
@@ -92,7 +99,9 @@ export default class ErrorBoundary extends Component<Props, State> {
         <ErrorView
           callStack={callStack}
           componentStack={componentStack}
-          dismissError={canDismiss ? this._dismissError : null}
+          dismissError={
+            canDismissProp || canDismissState ? this._dismissError : null
+          }
           errorMessage={errorMessage}>
           <Suspense fallback={<SearchingGitHubIssues />}>
             <SuspendingErrorView