diff --git a/packages/next/src/next-devtools/dev-overlay/container/errors.tsx b/packages/next/src/next-devtools/dev-overlay/container/errors.tsx index 4adf765c2c8cd..c9674c3efe1cd 100644 --- a/packages/next/src/next-devtools/dev-overlay/container/errors.tsx +++ b/packages/next/src/next-devtools/dev-overlay/container/errors.tsx @@ -1,11 +1,10 @@ -import { useState, useMemo, useRef, Suspense } from 'react' +import { useMemo, useRef, Suspense } from 'react' import type { DebugInfo } from '../../shared/types' import { Overlay, OverlayBackdrop } from '../components/overlay' import { RuntimeError } from './runtime-error' import { getErrorSource } from '../../../shared/lib/error-source' import { HotlinkedText } from '../components/hot-linked-text' import { PseudoHtmlDiff } from './runtime-error/component-stack-pseudo-html' -import { extractNextErrorCode } from '../../../lib/error-telemetry-utils' import { ErrorOverlayLayout, type ErrorOverlayLayoutProps, @@ -18,6 +17,7 @@ import { import type { ReadyRuntimeError } from '../utils/get-error-by-type' import type { ErrorBaseProps } from '../components/errors/error-overlay/error-overlay' import type { HydrationErrorState } from '../../shared/hydration-error' +import { useActiveRuntimeError } from '../hooks/use-active-runtime-error' export interface ErrorsProps extends ErrorBaseProps { getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null @@ -26,8 +26,6 @@ export interface ErrorsProps extends ErrorBaseProps { onClose: () => void } -type ReadyErrorEvent = ReadyRuntimeError - function isNextjsLink(text: string): boolean { return text.startsWith('https://nextjs.org') } @@ -55,7 +53,7 @@ function GenericErrorDescription({ error }: { error: Error }) { ) } -function getErrorTypeLabel( +export function getErrorTypeLabel( error: Error, type: ReadyRuntimeError['type'] ): ErrorOverlayLayoutProps['errorType'] { @@ -73,7 +71,7 @@ const noErrorDetails = { notes: null, reactOutputComponentDiff: null, } -function useErrorDetails( +export function useErrorDetails( error: Error | undefined, getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null ): { @@ -122,20 +120,17 @@ export function Errors({ }: ErrorsProps) { const dialogResizerRef = useRef(null) - const isLoading = useMemo(() => { - return runtimeErrors.length < 1 - }, [runtimeErrors.length]) - - const [activeIdx, setActiveIndex] = useState(0) - - const activeError = useMemo( - () => runtimeErrors[activeIdx] ?? null, - [activeIdx, runtimeErrors] - ) - const errorDetails = useErrorDetails( - activeError?.error, - getSquashedHydrationErrorDetails - ) + const { + isLoading, + errorCode, + errorType, + notes, + hydrationWarning, + activeIdx, + errorDetails, + activeError, + setActiveIndex, + } = useActiveRuntimeError({ runtimeErrors, getSquashedHydrationErrorDetails }) if (isLoading) { // TODO: better loading state @@ -154,12 +149,6 @@ export function Errors({ const isServerError = ['server', 'edge-server'].includes( getErrorSource(error) || '' ) - const errorType = getErrorTypeLabel(error, activeError.type) - // TOOD: May be better to always treat everything past the first blank line as notes - // We're currently only special casing hydration error messages. - const notes = errorDetails.notes - const hydrationWarning = errorDetails.hydrationWarning - const errorCode = extractNextErrorCode(error) return ( HydrationErrorState | null +}) { + const [activeIdx, setActiveIndex] = useState(0) + + const isLoading = useMemo(() => { + return runtimeErrors.length === 0 + }, [runtimeErrors.length]) + + const activeError = useMemo( + () => runtimeErrors[activeIdx] ?? null, + [activeIdx, runtimeErrors] + ) + + const errorDetails = useErrorDetails( + activeError?.error, + getSquashedHydrationErrorDetails + ) + + if (isLoading || !activeError) { + return { + isLoading, + activeIdx, + setActiveIndex, + activeError: null, + errorDetails: null, + errorCode: null, + errorType: null, + notes: null, + hydrationWarning: null, + } + } + + const error = activeError.error + const errorCode = extractNextErrorCode(error) + const errorType = getErrorTypeLabel(error, activeError.type) + + // TODO(GH#78140): May be better to always treat everything past the first blank line as notes + // We're currently only special casing hydration error messages. + const notes = errorDetails.notes + const hydrationWarning = errorDetails.hydrationWarning + + return { + isLoading, + activeIdx, + setActiveIndex, + activeError, + errorDetails, + errorCode, + errorType, + notes, + hydrationWarning, + } +}