From 3f1a61b18f9bf171180f4bd84fd54f922ac4cba0 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Wed, 12 Oct 2022 16:36:24 +0200 Subject: [PATCH] Ensure content is kept rendered below the error overlay on build errors in new router (#41360) - Remove unused code - Keep rendering underlying component tree when there is a build error - Move error catch up one level ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --- .../internal/ErrorBoundary.tsx | 33 ----- .../internal/ReactDevOverlay.tsx | 113 ++++++++++-------- .../internal/error-overlay-reducer.ts | 2 - 3 files changed, 66 insertions(+), 82 deletions(-) delete mode 100644 packages/next/client/components/react-dev-overlay/internal/ErrorBoundary.tsx diff --git a/packages/next/client/components/react-dev-overlay/internal/ErrorBoundary.tsx b/packages/next/client/components/react-dev-overlay/internal/ErrorBoundary.tsx deleted file mode 100644 index 4a7679b9d5223..0000000000000 --- a/packages/next/client/components/react-dev-overlay/internal/ErrorBoundary.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react' - -type ErrorBoundaryProps = { - isMounted?: boolean -} -type ErrorBoundaryState = { error: Error | null } - -class ErrorBoundary extends React.PureComponent< - ErrorBoundaryProps, - ErrorBoundaryState -> { - state = { error: null } - - static getDerivedStateFromError(error: Error) { - return { error } - } - - render() { - // The component has to be unmounted or else it would continue to error - return this.state.error || this.props.isMounted ? ( - // When the overlay is global for the application and it wraps a component rendering `` - // we have to render the html shell otherwise the shadow root will not be able to attach - - - - - ) : ( - this.props.children - ) - } -} - -export { ErrorBoundary } diff --git a/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx b/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx index 2de95e9a2b282..3dc527875923d 100644 --- a/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx +++ b/packages/next/client/components/react-dev-overlay/internal/ReactDevOverlay.tsx @@ -1,61 +1,80 @@ import * as React from 'react' -import type { OverlayState } from './error-overlay-reducer' +import { + ACTION_UNHANDLED_ERROR, + OverlayState, + UnhandledErrorAction, +} from './error-overlay-reducer' import { ShadowPortal } from './components/ShadowPortal' import { BuildError } from './container/BuildError' -import { Errors } from './container/Errors' -import { ErrorBoundary } from './ErrorBoundary' +import { Errors, SupportedErrorEvent } from './container/Errors' import { Base } from './styles/Base' import { ComponentStyles } from './styles/ComponentStyles' import { CssReset } from './styles/CssReset' +import { parseStack } from './helpers/parseStack' -type ErrorType = 'runtime' | 'build' +interface ReactDevOverlayState { + reactError: SupportedErrorEvent | null +} +class ReactDevOverlay extends React.PureComponent< + { + state: OverlayState + children: React.ReactNode + }, + ReactDevOverlayState +> { + state = { reactError: null } -const shouldPreventDisplay = ( - errorType?: ErrorType | null, - preventType?: ErrorType[] | null -) => { - if (!preventType || !errorType) { - return false + static getDerivedStateFromError(error: Error): ReactDevOverlayState { + const e = error + const event: UnhandledErrorAction = { + type: ACTION_UNHANDLED_ERROR, + reason: error, + frames: parseStack(e.stack!), + } + const errorEvent: SupportedErrorEvent = { + id: 0, + event, + } + return { reactError: errorEvent } } - return preventType.includes(errorType) -} -function ReactDevOverlay({ - state, - children, - preventDisplay, -}: { - state: OverlayState - children?: React.ReactNode - preventDisplay?: ErrorType[] -}) { - const hasBuildError = state.buildError != null - const hasRuntimeErrors = Boolean(state.errors.length) - - const isMounted = hasBuildError || hasRuntimeErrors - - return ( - <> - {children} - {isMounted ? ( - - - - - - {shouldPreventDisplay( - hasBuildError ? 'build' : hasRuntimeErrors ? 'runtime' : null, - preventDisplay - ) ? null : hasBuildError ? ( - - ) : hasRuntimeErrors ? ( - - ) : undefined} - - ) : undefined} - - ) + render() { + const { state, children } = this.props + const { reactError } = this.state + + const hasBuildError = state.buildError != null + const hasRuntimeErrors = Boolean(state.errors.length) + const isMounted = hasBuildError || hasRuntimeErrors || reactError + + return ( + <> + {reactError ? ( + + + + + ) : ( + children + )} + {isMounted ? ( + + + + + + {hasBuildError ? ( + + ) : hasRuntimeErrors ? ( + + ) : reactError ? ( + + ) : undefined} + + ) : undefined} + + ) + } } export default ReactDevOverlay diff --git a/packages/next/client/components/react-dev-overlay/internal/error-overlay-reducer.ts b/packages/next/client/components/react-dev-overlay/internal/error-overlay-reducer.ts index 2f0575166663a..d80a0172d599b 100644 --- a/packages/next/client/components/react-dev-overlay/internal/error-overlay-reducer.ts +++ b/packages/next/client/components/react-dev-overlay/internal/error-overlay-reducer.ts @@ -69,8 +69,6 @@ export function errorOverlayReducer( } } default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _: never = action return state } }