import {Component, ErrorInfo, lazy, Suspense, useMemo} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {t} from 'sentry/locale'; import {isWebpackChunkLoadingError} from 'sentry/utils'; import retryableImport from 'sentry/utils/retryableImport'; type PromisedImport = Promise<{default: C}>; type ComponentType = React.ComponentType; type Props = Omit, 'route'> & { /** * Accepts a function to trigger the import resolution of the component. */ component: () => PromisedImport; }; /** * LazyLoad is used to dynamically load codesplit components via a `import` * call. This is primarily used in our routing tree. * * import('./myComponent')} someComponentProps={...} /> */ function LazyLoad({component, ...props}: Props) { const LazyComponent = useMemo( () => lazy(() => retryableImport(component)), [component] ); return ( } > )} /> ); } interface ErrorBoundaryState { error: Error | null; hasError: boolean; } // Error boundaries currently have to be classes. class ErrorBoundary extends Component<{}, ErrorBoundaryState> { static getDerivedStateFromError(error: Error) { return { hasError: true, error, }; } state = {hasError: false, error: null}; componentDidCatch(error: Error, errorInfo: ErrorInfo) { Sentry.withScope(scope => { if (isWebpackChunkLoadingError(error)) { scope.setFingerprint(['webpack', 'error loading chunk']); } scope.setExtra('errorInfo', errorInfo); Sentry.captureException(error); }); // eslint-disable-next-line no-console console.error(error); } // Reset `hasError` so that we attempt to render `this.props.children` again handleRetry = () => this.setState({hasError: false}); render() { if (this.state.hasError) { return ( ); } return this.props.children; } } const LoadingContainer = styled('div')` display: flex; flex: 1; align-items: center; `; const LoadingErrorContainer = styled('div')` flex: 1; `; export default LazyLoad;