Skip to content

Commit 56be75a

Browse files
committed
[devtools] port runtime error handling to hook
1 parent c572a5e commit 56be75a

File tree

2 files changed

+83
-26
lines changed

2 files changed

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

0 commit comments

Comments
 (0)