Skip to content

[devtools] port runtime error handling to hook #80567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 15 additions & 26 deletions packages/next/src/next-devtools/dev-overlay/container/errors.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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
Expand All @@ -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')
}
Expand Down Expand Up @@ -55,7 +53,7 @@ function GenericErrorDescription({ error }: { error: Error }) {
)
}

function getErrorTypeLabel(
export function getErrorTypeLabel(
error: Error,
type: ReadyRuntimeError['type']
): ErrorOverlayLayoutProps['errorType'] {
Expand All @@ -73,7 +71,7 @@ const noErrorDetails = {
notes: null,
reactOutputComponentDiff: null,
}
function useErrorDetails(
export function useErrorDetails(
error: Error | undefined,
getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null
): {
Expand Down Expand Up @@ -122,20 +120,17 @@ export function Errors({
}: ErrorsProps) {
const dialogResizerRef = useRef<HTMLDivElement | null>(null)

const isLoading = useMemo<boolean>(() => {
return runtimeErrors.length < 1
}, [runtimeErrors.length])

const [activeIdx, setActiveIndex] = useState<number>(0)

const activeError = useMemo<ReadyErrorEvent | null>(
() => 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
Expand All @@ -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 (
<ErrorOverlayLayout
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { ReadyRuntimeError } from '../utils/get-error-by-type'
import type { HydrationErrorState } from '../../shared/hydration-error'

import { useMemo, useState } from 'react'
import { getErrorTypeLabel, useErrorDetails } from '../container/errors'
import { extractNextErrorCode } from '../../../lib/error-telemetry-utils'

export function useActiveRuntimeError({
runtimeErrors,
getSquashedHydrationErrorDetails,
}: {
runtimeErrors: ReadyRuntimeError[]
getSquashedHydrationErrorDetails: (error: Error) => HydrationErrorState | null
}) {
const [activeIdx, setActiveIndex] = useState<number>(0)

const isLoading = useMemo<boolean>(() => {
return runtimeErrors.length === 0
}, [runtimeErrors.length])

const activeError = useMemo<ReadyRuntimeError | null>(
() => 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to include the GH PR that introduced this TODO. That's what git-blame is for.

// 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,
}
}
Loading