Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
reportInvalidHmrMessage,
useErrorOverlayReducer,
} from '../shared'
import { parseStack } from '../utils/parse-stack'
import { AppDevOverlay } from './app-dev-overlay'
import { useErrorHandler } from '../../errors/use-error-handler'
import { RuntimeErrorHandler } from '../../errors/runtime-error-handler'
Expand All @@ -30,7 +29,6 @@ import {
useWebsocket,
useWebsocketPing,
} from '../utils/use-websocket'
import { parseComponentStack } from '../utils/parse-component-stack'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../../server/dev/hot-reloader-types'
import type {
Expand All @@ -40,7 +38,6 @@ import type {
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from '../shared'
import type { DebugInfo } from '../types'
import { useUntrackedPathname } from '../../navigation-untracked'
import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
import { handleDevBuildIndicatorHmrEvents } from '../../../dev/dev-build-indicator/internal/handle-dev-build-indicator-hmr-events'
import type { GlobalErrorComponent } from '../../global-error'
import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicator-server-state'
Expand Down Expand Up @@ -514,26 +511,15 @@ export default function HotReload({
})
},
onUnhandledError(error) {
// Component stack is added to the error in use-error-handler in case there was a hydration error
const componentStack = getComponentStack(error)
const ownerStack = getOwnerStack(error)

dispatch({
type: ACTION_UNHANDLED_ERROR,
reason: error,
frames: parseStack((error.stack || '') + (ownerStack || '')),
componentStackFrames:
typeof componentStack === 'string'
? parseComponentStack(componentStack)
: undefined,
})
},
onUnhandledRejection(error) {
const ownerStack = getOwnerStack(error)
dispatch({
type: ACTION_UNHANDLED_REJECTION,
reason: error,
frames: parseStack((error.stack || '') + (ownerStack || '')),
})
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as Bus from './bus'
import { parseStack } from '../utils/parse-stack'
import { parseComponentStack } from '../utils/parse-component-stack'
import {
attachHydrationErrorState,
storeHydrationErrorStateFromConsoleArgs,
Expand All @@ -17,7 +15,6 @@ import {
ACTION_VERSION_INFO,
} from '../shared'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicator-server-state'

let isRegistered = false
Expand All @@ -30,13 +27,6 @@ function handleError(error: unknown) {

attachHydrationErrorState(error)

const componentStackTrace = getComponentStack(error)
const ownerStack = getOwnerStack(error)
const componentStackFrames =
typeof componentStackTrace === 'string'
? parseComponentStack(componentStackTrace)
: undefined

// Skip ModuleBuildError and ModuleNotFoundError, as it will be sent through onBuildError callback.
// This is to avoid same error as different type showing up on client to cause flashing.
if (
Expand All @@ -46,8 +36,6 @@ function handleError(error: unknown) {
Bus.emit({
type: ACTION_UNHANDLED_ERROR,
reason: error,
frames: parseStack((error.stack || '') + (ownerStack || '')),
componentStackFrames,
})
}
}
Expand Down Expand Up @@ -78,12 +66,9 @@ function onUnhandledRejection(ev: PromiseRejectionEvent) {
return
}

const error = reason
const ownerStack = getOwnerStack(error)
Bus.emit({
type: ACTION_UNHANDLED_REJECTION,
reason: reason,
frames: parseStack((error.stack || '') + (ownerStack || '')),
})
}

Expand Down
59 changes: 36 additions & 23 deletions packages/next/src/client/components/react-dev-overlay/shared.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useReducer } from 'react'

import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import type { VersionInfo } from '../../../server/dev/parse-version-info'
import type { SupportedErrorEvent } from './ui/container/runtime-error/render-error'
import type { ComponentStackFrame } from './utils/parse-component-stack'
import { parseComponentStack } from './utils/parse-component-stack'
import type { DebugInfo } from './types'
import type { DevIndicatorServerState } from '../../../server/dev/dev-indicator-server-state'
import type { HMR_ACTION_TYPES } from '../../../server/dev/hot-reloader-types'
import { getOwnerStack } from '../errors/stitched-error'
import { parseStack } from './utils/parse-stack'
import { getComponentStack, getOwnerStack } from '../errors/stitched-error'

type FastRefreshState =
/** No refresh in progress. */
Expand Down Expand Up @@ -71,13 +71,10 @@ interface FastRefreshAction {
export interface UnhandledErrorAction {
type: typeof ACTION_UNHANDLED_ERROR
reason: Error
frames: StackFrame[]
componentStackFrames?: ComponentStackFrame[]
}
export interface UnhandledRejectionAction {
type: typeof ACTION_UNHANDLED_REJECTION
reason: Error
frames: StackFrame[]
}

export interface DebugInfoAction {
Expand Down Expand Up @@ -134,21 +131,35 @@ function getStackIgnoringStrictMode(stack: string | undefined) {
}

function pushErrorFilterDuplicates(
errors: SupportedErrorEvent[],
err: SupportedErrorEvent
events: SupportedErrorEvent[],
id: number,
error: Error
): SupportedErrorEvent[] {
const pendingErrors = errors.filter((e) => {
const componentStack = getComponentStack(error)
const componentStackFrames =
componentStack === undefined
? undefined
: parseComponentStack(componentStack)
const ownerStack = getOwnerStack(error)
const frames = parseStack((error.stack || '') + (ownerStack || ''))
const pendingEvent: SupportedErrorEvent = {
id,
error,
frames,
componentStackFrames,
}
const pendingEvents = events.filter((event) => {
// Filter out duplicate errors
return (
(e.event.reason.stack !== err.event.reason.stack &&
(event.error.stack !== pendingEvent.error.stack &&
// TODO: Let ReactDevTools control deduping instead?
getStackIgnoringStrictMode(e.event.reason.stack) !==
getStackIgnoringStrictMode(err.event.reason.stack)) ||
getOwnerStack(e.event.reason) !== getOwnerStack(err.event.reason)
getStackIgnoringStrictMode(event.error.stack) !==
getStackIgnoringStrictMode(pendingEvent.error.stack)) ||
getOwnerStack(event.error) !== getOwnerStack(pendingEvent.error)
)
})
pendingErrors.push(err)
return pendingErrors
pendingEvents.push(pendingEvent)
return pendingEvents
}

const shouldDisableDevIndicator =
Expand Down Expand Up @@ -230,10 +241,11 @@ export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
return {
...state,
nextId: state.nextId + 1,
errors: pushErrorFilterDuplicates(state.errors, {
id: state.nextId,
event: action,
}),
errors: pushErrorFilterDuplicates(
state.errors,
state.nextId,
action.reason
),
}
}
case 'pending': {
Expand All @@ -242,10 +254,11 @@ export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
nextId: state.nextId + 1,
refreshState: {
...state.refreshState,
errors: pushErrorFilterDuplicates(state.refreshState.errors, {
id: state.nextId,
event: action,
}),
errors: pushErrorFilterDuplicates(
state.errors,
state.nextId,
action.reason
),
},
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type {
OverlayState,
UnhandledErrorAction,
UnhandledRejectionAction,
} from '../../../shared'
import type { OverlayState } from '../../../shared'

import { useMemo, useState, useEffect } from 'react'
import {
getErrorByType,
type ReadyRuntimeError,
} from '../../../utils/get-error-by-type'
import type { StackFrame } from 'next/dist/compiled/stacktrace-parser'
import type { ComponentStackFrame } from '../../../utils/parse-component-stack'

export type SupportedErrorEvent = {
id: number
event: UnhandledErrorAction | UnhandledRejectionAction
error: Error
frames: StackFrame[]
componentStackFrames?: ComponentStackFrame[]
}

type Props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
ACTION_ERROR_OVERLAY_CLOSE,
ACTION_ERROR_OVERLAY_OPEN,
ACTION_ERROR_OVERLAY_TOGGLE,
ACTION_UNHANDLED_ERROR,
} from '../shared'

const meta: Meta<typeof DevOverlay> = {
Expand All @@ -32,50 +31,41 @@ const initialState: OverlayState = {
errors: [
{
id: 1,
event: {
type: ACTION_UNHANDLED_ERROR,
reason: Object.assign(new Error('First error message'), {
__NEXT_ERROR_CODE: 'E001',
}),
componentStackFrames: [
{
file: 'app/page.tsx',
component: 'Home',
lineNumber: 10,
column: 5,
canOpenInEditor: true,
},
],
frames: [
{
file: 'app/page.tsx',
methodName: 'Home',
arguments: [],
lineNumber: 10,
column: 5,
},
],
},
error: Object.assign(new Error('First error message'), {
__NEXT_ERROR_CODE: 'E001',
}),
componentStackFrames: [
{
file: 'app/page.tsx',
component: 'Home',
lineNumber: 10,
column: 5,
canOpenInEditor: true,
},
],
frames: [
{
file: 'app/page.tsx',
methodName: 'Home',
arguments: [],
lineNumber: 10,
column: 5,
},
],
},
{
id: 2,
event: {
type: ACTION_UNHANDLED_ERROR,
reason: Object.assign(new Error('Second error message'), {
__NEXT_ERROR_CODE: 'E002',
}),
frames: [],
},
error: Object.assign(new Error('Second error message'), {
__NEXT_ERROR_CODE: 'E002',
}),
frames: [],
},
{
id: 3,
event: {
type: ACTION_UNHANDLED_ERROR,
reason: Object.assign(new Error('Third error message'), {
__NEXT_ERROR_CODE: 'E003',
}),
frames: [],
},
error: Object.assign(new Error('Third error message'), {
__NEXT_ERROR_CODE: 'E003',
}),
frames: [],
},
],
refreshState: { type: 'idle' },
Expand Down
Loading
Loading