Skip to content

Commit 092cede

Browse files
committed
[dev-overlay] Remove indirection in app dev error boundary
Doesn't need to be put in JSX children and can directly returned from render. Allows for cleaner code separation with the future dedicated dev overlay bundle.
1 parent bfdd74b commit 092cede

File tree

3 files changed

+83
-82
lines changed

3 files changed

+83
-82
lines changed

packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay-error-boundary.tsx

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import { PureComponent } from 'react'
1+
import { PureComponent, useEffect } from 'react'
22
import { RuntimeErrorHandler } from '../../errors/runtime-error-handler'
3+
import { handleClientError } from '../../errors/use-error-handler'
34
import { ErrorBoundary } from '../../error-boundary'
45
import DefaultGlobalError, {
56
type GlobalErrorComponent,
67
} from '../../global-error'
8+
import { isNextRouterError } from '../../is-next-router-error'
9+
import { ACTION_ERROR_OVERLAY_OPEN, type OverlayDispatch } from '../shared'
10+
import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants'
711

812
type AppDevOverlayErrorBoundaryProps = {
913
children: React.ReactNode
1014
globalError: [GlobalErrorComponent, React.ReactNode]
11-
onError: () => void
15+
dispatch: OverlayDispatch
1216
}
1317

1418
type AppDevOverlayErrorBoundaryState = {
@@ -38,6 +42,65 @@ function ErroredHtml({
3842
)
3943
}
4044

45+
function readSsrError(): (Error & { digest?: string }) | null {
46+
if (typeof document === 'undefined') {
47+
return null
48+
}
49+
50+
const ssrErrorTemplateTag = document.querySelector(
51+
'template[data-next-error-message]'
52+
)
53+
if (ssrErrorTemplateTag) {
54+
const message: string = ssrErrorTemplateTag.getAttribute(
55+
'data-next-error-message'
56+
)!
57+
const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack')
58+
const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest')
59+
const error = new Error(message)
60+
if (digest) {
61+
;(error as any).digest = digest
62+
}
63+
// Skip Next.js SSR'd internal errors that which will be handled by the error boundaries.
64+
if (isNextRouterError(error)) {
65+
return null
66+
}
67+
error.stack = stack || ''
68+
return error
69+
}
70+
71+
return null
72+
}
73+
74+
// Needs to be in the same error boundary as the shell.
75+
// If it commits, we know we recovered from an SSR error.
76+
// If it doesn't commit, we errored again and React will take care of error reporting.
77+
function ReplaySsrOnlyErrors({
78+
onBlockingError,
79+
}: {
80+
onBlockingError: () => void
81+
}) {
82+
if (process.env.NODE_ENV !== 'production') {
83+
// Need to read during render. The attributes will be gone after commit.
84+
const ssrError = readSsrError()
85+
// eslint-disable-next-line react-hooks/rules-of-hooks
86+
useEffect(() => {
87+
if (ssrError !== null) {
88+
// TODO(veil): Include original Owner Stack (NDX-905)
89+
// TODO(veil): Mark as recoverable error
90+
// TODO(veil): console.error
91+
handleClientError(ssrError)
92+
93+
// If it's missing root tags, we can't recover, make it blocking.
94+
if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) {
95+
onBlockingError()
96+
}
97+
}
98+
}, [ssrError, onBlockingError])
99+
}
100+
101+
return null
102+
}
103+
41104
export class AppDevOverlayErrorBoundary extends PureComponent<
42105
AppDevOverlayErrorBoundaryProps,
43106
AppDevOverlayErrorBoundaryState
@@ -52,8 +115,14 @@ export class AppDevOverlayErrorBoundary extends PureComponent<
52115
}
53116
}
54117

118+
openErrorOverlay = () => {
119+
this.props.dispatch({
120+
type: ACTION_ERROR_OVERLAY_OPEN,
121+
})
122+
}
123+
55124
componentDidCatch() {
56-
this.props.onError()
125+
this.openErrorOverlay()
57126
}
58127

59128
render() {
@@ -64,6 +133,13 @@ export class AppDevOverlayErrorBoundary extends PureComponent<
64133
<ErroredHtml globalError={globalError} error={reactError} />
65134
)
66135

67-
return reactError !== null ? fallback : children
136+
return reactError !== null ? (
137+
fallback
138+
) : (
139+
<>
140+
<ReplaySsrOnlyErrors onBlockingError={this.openErrorOverlay} />
141+
{children}
142+
</>
143+
)
68144
}
69145
}

packages/next/src/client/components/react-dev-overlay/app/app-dev-overlay.tsx

Lines changed: 2 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,9 @@
1-
import {
2-
ACTION_ERROR_OVERLAY_OPEN,
3-
type OverlayDispatch,
4-
type OverlayState,
5-
} from '../shared'
1+
import type { OverlayDispatch, OverlayState } from '../shared'
62
import type { GlobalErrorComponent } from '../../global-error'
73

8-
import { useCallback, useEffect } from 'react'
94
import { AppDevOverlayErrorBoundary } from './app-dev-overlay-error-boundary'
105
import { FontStyles } from '../font/font-styles'
116
import { DevOverlay } from '../ui/dev-overlay'
12-
import { handleClientError } from '../../errors/use-error-handler'
13-
import { isNextRouterError } from '../../is-next-router-error'
14-
import { MISSING_ROOT_TAGS_ERROR } from '../../../../shared/lib/errors/constants'
15-
16-
function readSsrError(): (Error & { digest?: string }) | null {
17-
if (typeof document === 'undefined') {
18-
return null
19-
}
20-
21-
const ssrErrorTemplateTag = document.querySelector(
22-
'template[data-next-error-message]'
23-
)
24-
if (ssrErrorTemplateTag) {
25-
const message: string = ssrErrorTemplateTag.getAttribute(
26-
'data-next-error-message'
27-
)!
28-
const stack = ssrErrorTemplateTag.getAttribute('data-next-error-stack')
29-
const digest = ssrErrorTemplateTag.getAttribute('data-next-error-digest')
30-
const error = new Error(message)
31-
if (digest) {
32-
;(error as any).digest = digest
33-
}
34-
// Skip Next.js SSR'd internal errors that which will be handled by the error boundaries.
35-
if (isNextRouterError(error)) {
36-
return null
37-
}
38-
error.stack = stack || ''
39-
return error
40-
}
41-
42-
return null
43-
}
44-
45-
// Needs to be in the same error boundary as the shell.
46-
// If it commits, we know we recovered from an SSR error.
47-
// If it doesn't commit, we errored again and React will take care of error reporting.
48-
function ReplaySsrOnlyErrors({
49-
onBlockingError,
50-
}: {
51-
onBlockingError: () => void
52-
}) {
53-
if (process.env.NODE_ENV !== 'production') {
54-
// Need to read during render. The attributes will be gone after commit.
55-
const ssrError = readSsrError()
56-
// eslint-disable-next-line react-hooks/rules-of-hooks
57-
useEffect(() => {
58-
if (ssrError !== null) {
59-
// TODO(veil): Include original Owner Stack (NDX-905)
60-
// TODO(veil): Mark as recoverable error
61-
// TODO(veil): console.error
62-
handleClientError(ssrError)
63-
64-
// If it's missing root tags, we can't recover, make it blocking.
65-
if (ssrError.digest === MISSING_ROOT_TAGS_ERROR) {
66-
onBlockingError()
67-
}
68-
}
69-
}, [ssrError, onBlockingError])
70-
}
71-
72-
return null
73-
}
747

758
export function AppDevOverlay({
769
state,
@@ -83,17 +16,9 @@ export function AppDevOverlay({
8316
globalError: [GlobalErrorComponent, React.ReactNode]
8417
children: React.ReactNode
8518
}) {
86-
const openOverlay = useCallback(() => {
87-
dispatch({ type: ACTION_ERROR_OVERLAY_OPEN })
88-
}, [dispatch])
89-
9019
return (
9120
<>
92-
<AppDevOverlayErrorBoundary
93-
globalError={globalError}
94-
onError={openOverlay}
95-
>
96-
<ReplaySsrOnlyErrors onBlockingError={openOverlay} />
21+
<AppDevOverlayErrorBoundary globalError={globalError} dispatch={dispatch}>
9722
{children}
9823
</AppDevOverlayErrorBoundary>
9924
<>

packages/next/src/client/components/react-dev-overlay/app/client-entry.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function createRootLevelDevOverlayElement(reactEl: React.ReactElement) {
3333
return (
3434
<AppDevOverlayErrorBoundary
3535
globalError={[GlobalError, null]}
36-
onError={noop}
36+
dispatch={noop}
3737
>
3838
{reactEl}
3939
</AppDevOverlayErrorBoundary>

0 commit comments

Comments
 (0)