Skip to content

Commit ac59bda

Browse files
authored
[devtools] port runtime error handling to hook (#80567)
This will be used for sharing the error across the Error Overlay and the DevTools Panel.
1 parent 0fbbb6a commit ac59bda

File tree

2 files changed

+80
-26
lines changed

2 files changed

+80
-26
lines changed

packages/next/src/next-devtools/dev-overlay/container/errors.tsx

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useState, useMemo, useRef, Suspense } from 'react'
1+
import { useMemo, useRef, Suspense } from 'react'
22
import type { DebugInfo } from '../../shared/types'
33
import { Overlay, OverlayBackdrop } from '../components/overlay'
44
import { RuntimeError } from './runtime-error'
55
import { getErrorSource } from '../../../shared/lib/error-source'
66
import { HotlinkedText } from '../components/hot-linked-text'
77
import { PseudoHtmlDiff } from './runtime-error/component-stack-pseudo-html'
8-
import { extractNextErrorCode } from '../../../lib/error-telemetry-utils'
98
import {
109
ErrorOverlayLayout,
1110
type ErrorOverlayLayoutProps,
@@ -18,6 +17,7 @@ import {
1817
import type { ReadyRuntimeError } from '../utils/get-error-by-type'
1918
import type { ErrorBaseProps } from '../components/errors/error-overlay/error-overlay'
2019
import type { HydrationErrorState } from '../../shared/hydration-error'
20+
import { useActiveRuntimeError } from '../hooks/use-active-runtime-error'
2121

2222
export interface ErrorsProps extends ErrorBaseProps {
2323
getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null
@@ -26,8 +26,6 @@ export interface ErrorsProps extends ErrorBaseProps {
2626
onClose: () => void
2727
}
2828

29-
type ReadyErrorEvent = ReadyRuntimeError
30-
3129
function isNextjsLink(text: string): boolean {
3230
return text.startsWith('https://nextjs.org')
3331
}
@@ -55,7 +53,7 @@ function GenericErrorDescription({ error }: { error: Error }) {
5553
)
5654
}
5755

58-
function getErrorTypeLabel(
56+
export function getErrorTypeLabel(
5957
error: Error,
6058
type: ReadyRuntimeError['type']
6159
): ErrorOverlayLayoutProps['errorType'] {
@@ -73,7 +71,7 @@ const noErrorDetails = {
7371
notes: null,
7472
reactOutputComponentDiff: null,
7573
}
76-
function useErrorDetails(
74+
export function useErrorDetails(
7775
error: Error | undefined,
7876
getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null
7977
): {
@@ -122,20 +120,17 @@ export function Errors({
122120
}: ErrorsProps) {
123121
const dialogResizerRef = useRef<HTMLDivElement | null>(null)
124122

125-
const isLoading = useMemo<boolean>(() => {
126-
return runtimeErrors.length < 1
127-
}, [runtimeErrors.length])
128-
129-
const [activeIdx, setActiveIndex] = useState<number>(0)
130-
131-
const activeError = useMemo<ReadyErrorEvent | null>(
132-
() => runtimeErrors[activeIdx] ?? null,
133-
[activeIdx, runtimeErrors]
134-
)
135-
const errorDetails = useErrorDetails(
136-
activeError?.error,
137-
getSquashedHydrationErrorDetails
138-
)
123+
const {
124+
isLoading,
125+
errorCode,
126+
errorType,
127+
notes,
128+
hydrationWarning,
129+
activeIdx,
130+
errorDetails,
131+
activeError,
132+
setActiveIndex,
133+
} = useActiveRuntimeError({ runtimeErrors, getSquashedHydrationErrorDetails })
139134

140135
if (isLoading) {
141136
// TODO: better loading state
@@ -154,12 +149,6 @@ export function Errors({
154149
const isServerError = ['server', 'edge-server'].includes(
155150
getErrorSource(error) || ''
156151
)
157-
const errorType = getErrorTypeLabel(error, activeError.type)
158-
// TOOD: May be better to always treat everything past the first blank line as notes
159-
// We're currently only special casing hydration error messages.
160-
const notes = errorDetails.notes
161-
const hydrationWarning = errorDetails.hydrationWarning
162-
const errorCode = extractNextErrorCode(error)
163152

164153
return (
165154
<ErrorOverlayLayout
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import type { ReadyRuntimeError } from '../utils/get-error-by-type'
2+
import type { HydrationErrorState } from '../../shared/hydration-error'
3+
4+
import { useMemo, useState } from 'react'
5+
import { getErrorTypeLabel, useErrorDetails } from '../container/errors'
6+
import { extractNextErrorCode } from '../../../lib/error-telemetry-utils'
7+
8+
export function useActiveRuntimeError({
9+
runtimeErrors,
10+
getSquashedHydrationErrorDetails,
11+
}: {
12+
runtimeErrors: ReadyRuntimeError[]
13+
getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null
14+
}) {
15+
const [activeIdx, setActiveIndex] = useState<number>(0)
16+
17+
const isLoading = useMemo<boolean>(() => {
18+
return runtimeErrors.length === 0
19+
}, [runtimeErrors.length])
20+
21+
const activeError = useMemo<ReadyRuntimeError | null>(
22+
() => runtimeErrors[activeIdx] ?? null,
23+
[activeIdx, runtimeErrors]
24+
)
25+
26+
const errorDetails = useErrorDetails(
27+
activeError?.error,
28+
getSquashedHydrationErrorDetails
29+
)
30+
31+
if (isLoading || !activeError) {
32+
return {
33+
isLoading,
34+
activeIdx,
35+
setActiveIndex,
36+
activeError: null,
37+
errorDetails: null,
38+
errorCode: null,
39+
errorType: null,
40+
notes: null,
41+
hydrationWarning: null,
42+
}
43+
}
44+
45+
const error = activeError.error
46+
const errorCode = extractNextErrorCode(error)
47+
const errorType = getErrorTypeLabel(error, activeError.type)
48+
49+
// TODO(GH#78140): May be better to always treat everything past the first blank line as notes
50+
// We're currently only special casing hydration error messages.
51+
const notes = errorDetails.notes
52+
const hydrationWarning = errorDetails.hydrationWarning
53+
54+
return {
55+
isLoading,
56+
activeIdx,
57+
setActiveIndex,
58+
activeError,
59+
errorDetails,
60+
errorCode,
61+
errorType,
62+
notes,
63+
hydrationWarning,
64+
}
65+
}

0 commit comments

Comments
 (0)