1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import * as React from 'react';
    
  11. import {Component, Suspense} from 'react';
    
  12. import Store from 'react-devtools-shared/src/devtools/store';
    
  13. import UnsupportedBridgeOperationView from './UnsupportedBridgeOperationView';
    
  14. import ErrorView from './ErrorView';
    
  15. import SearchingGitHubIssues from './SearchingGitHubIssues';
    
  16. import SuspendingErrorView from './SuspendingErrorView';
    
  17. import TimeoutView from './TimeoutView';
    
  18. import CaughtErrorView from './CaughtErrorView';
    
  19. import UnsupportedBridgeOperationError from 'react-devtools-shared/src/UnsupportedBridgeOperationError';
    
  20. import TimeoutError from 'react-devtools-shared/src/errors/TimeoutError';
    
  21. import UserError from 'react-devtools-shared/src/errors/UserError';
    
  22. import UnknownHookError from 'react-devtools-shared/src/errors/UnknownHookError';
    
  23. import {logEvent} from 'react-devtools-shared/src/Logger';
    
  24. 
    
  25. type Props = {
    
  26.   children: React$Node,
    
  27.   canDismiss?: boolean,
    
  28.   onBeforeDismissCallback?: () => void,
    
  29.   store?: Store,
    
  30. };
    
  31. 
    
  32. type State = {
    
  33.   callStack: string | null,
    
  34.   canDismiss: boolean,
    
  35.   componentStack: string | null,
    
  36.   errorMessage: string | null,
    
  37.   hasError: boolean,
    
  38.   isUnsupportedBridgeOperationError: boolean,
    
  39.   isTimeout: boolean,
    
  40.   isUserError: boolean,
    
  41.   isUnknownHookError: boolean,
    
  42. };
    
  43. 
    
  44. const InitialState: State = {
    
  45.   callStack: null,
    
  46.   canDismiss: false,
    
  47.   componentStack: null,
    
  48.   errorMessage: null,
    
  49.   hasError: false,
    
  50.   isUnsupportedBridgeOperationError: false,
    
  51.   isTimeout: false,
    
  52.   isUserError: false,
    
  53.   isUnknownHookError: false,
    
  54. };
    
  55. 
    
  56. export default class ErrorBoundary extends Component<Props, State> {
    
  57.   state: State = InitialState;
    
  58. 
    
  59.   static getDerivedStateFromError(error: any): {
    
  60.     callStack: string | null,
    
  61.     errorMessage: string | null,
    
  62.     hasError: boolean,
    
  63.     isTimeout: boolean,
    
  64.     isUnknownHookError: boolean,
    
  65.     isUnsupportedBridgeOperationError: boolean,
    
  66.     isUserError: boolean,
    
  67.   } {
    
  68.     const errorMessage =
    
  69.       typeof error === 'object' &&
    
  70.       error !== null &&
    
  71.       typeof error.message === 'string'
    
  72.         ? error.message
    
  73.         : null;
    
  74. 
    
  75.     const isTimeout = error instanceof TimeoutError;
    
  76.     const isUserError = error instanceof UserError;
    
  77.     const isUnknownHookError = error instanceof UnknownHookError;
    
  78.     const isUnsupportedBridgeOperationError =
    
  79.       error instanceof UnsupportedBridgeOperationError;
    
  80. 
    
  81.     const callStack =
    
  82.       typeof error === 'object' &&
    
  83.       error !== null &&
    
  84.       typeof error.stack === 'string'
    
  85.         ? error.stack.split('\n').slice(1).join('\n')
    
  86.         : null;
    
  87. 
    
  88.     return {
    
  89.       callStack,
    
  90.       errorMessage,
    
  91.       hasError: true,
    
  92.       isUnsupportedBridgeOperationError,
    
  93.       isUnknownHookError,
    
  94.       isTimeout,
    
  95.       isUserError,
    
  96.     };
    
  97.   }
    
  98. 
    
  99.   componentDidCatch(error: any, {componentStack}: any) {
    
  100.     this._logError(error, componentStack);
    
  101.     this.setState({
    
  102.       componentStack,
    
  103.     });
    
  104.   }
    
  105. 
    
  106.   componentDidMount() {
    
  107.     const {store} = this.props;
    
  108.     if (store != null) {
    
  109.       store.addListener('error', this._onStoreError);
    
  110.     }
    
  111.   }
    
  112. 
    
  113.   componentWillUnmount() {
    
  114.     const {store} = this.props;
    
  115.     if (store != null) {
    
  116.       store.removeListener('error', this._onStoreError);
    
  117.     }
    
  118.   }
    
  119. 
    
  120.   render(): React.Node {
    
  121.     const {canDismiss: canDismissProp, children} = this.props;
    
  122.     const {
    
  123.       callStack,
    
  124.       canDismiss: canDismissState,
    
  125.       componentStack,
    
  126.       errorMessage,
    
  127.       hasError,
    
  128.       isUnsupportedBridgeOperationError,
    
  129.       isTimeout,
    
  130.       isUserError,
    
  131.       isUnknownHookError,
    
  132.     } = this.state;
    
  133. 
    
  134.     if (hasError) {
    
  135.       if (isTimeout) {
    
  136.         return (
    
  137.           <TimeoutView
    
  138.             callStack={callStack}
    
  139.             componentStack={componentStack}
    
  140.             dismissError={
    
  141.               canDismissProp || canDismissState ? this._dismissError : null
    
  142.             }
    
  143.             errorMessage={errorMessage}
    
  144.           />
    
  145.         );
    
  146.       } else if (isUnsupportedBridgeOperationError) {
    
  147.         return (
    
  148.           <UnsupportedBridgeOperationView
    
  149.             callStack={callStack}
    
  150.             componentStack={componentStack}
    
  151.             errorMessage={errorMessage}
    
  152.           />
    
  153.         );
    
  154.       } else if (isUserError) {
    
  155.         return (
    
  156.           <CaughtErrorView
    
  157.             callStack={callStack}
    
  158.             componentStack={componentStack}
    
  159.             errorMessage={errorMessage || 'Error occured in inspected element'}
    
  160.             info={
    
  161.               <>
    
  162.                 React DevTools encountered an error while trying to inspect the
    
  163.                 hooks. This is most likely caused by a developer error in the
    
  164.                 currently inspected element. Please see your console for logged
    
  165.                 error.
    
  166.               </>
    
  167.             }
    
  168.           />
    
  169.         );
    
  170.       } else if (isUnknownHookError) {
    
  171.         return (
    
  172.           <CaughtErrorView
    
  173.             callStack={callStack}
    
  174.             componentStack={componentStack}
    
  175.             errorMessage={errorMessage || 'Encountered an unknown hook'}
    
  176.             info={
    
  177.               <>
    
  178.                 React DevTools encountered an unknown hook. This is probably
    
  179.                 because the react-debug-tools package is out of date. To fix,
    
  180.                 upgrade the React DevTools to the most recent version.
    
  181.               </>
    
  182.             }
    
  183.           />
    
  184.         );
    
  185.       } else {
    
  186.         return (
    
  187.           <ErrorView
    
  188.             callStack={callStack}
    
  189.             componentStack={componentStack}
    
  190.             dismissError={
    
  191.               canDismissProp || canDismissState ? this._dismissError : null
    
  192.             }
    
  193.             errorMessage={errorMessage}>
    
  194.             <Suspense fallback={<SearchingGitHubIssues />}>
    
  195.               <SuspendingErrorView
    
  196.                 callStack={callStack}
    
  197.                 componentStack={componentStack}
    
  198.                 errorMessage={errorMessage}
    
  199.               />
    
  200.             </Suspense>
    
  201.           </ErrorView>
    
  202.         );
    
  203.       }
    
  204.     }
    
  205. 
    
  206.     return children;
    
  207.   }
    
  208. 
    
  209.   _logError: (error: any, componentStack: string | null) => void = (
    
  210.     error,
    
  211.     componentStack,
    
  212.   ) => {
    
  213.     logEvent({
    
  214.       event_name: 'error',
    
  215.       error_message: error.message ?? null,
    
  216.       error_stack: error.stack ?? null,
    
  217.       error_component_stack: componentStack ?? null,
    
  218.     });
    
  219.   };
    
  220. 
    
  221.   _dismissError: () => void = () => {
    
  222.     const onBeforeDismissCallback = this.props.onBeforeDismissCallback;
    
  223.     if (typeof onBeforeDismissCallback === 'function') {
    
  224.       onBeforeDismissCallback();
    
  225.     }
    
  226. 
    
  227.     this.setState(InitialState);
    
  228.   };
    
  229. 
    
  230.   _onStoreError: (error: Error) => void = error => {
    
  231.     if (!this.state.hasError) {
    
  232.       this._logError(error, null);
    
  233.       this.setState({
    
  234.         ...ErrorBoundary.getDerivedStateFromError(error),
    
  235.         canDismiss: true,
    
  236.       });
    
  237.     }
    
  238.   };
    
  239. }