Skip to content

Commit

Permalink
refactor: reorganize client error listeners (#70373)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Sep 24, 2024
1 parent 5622c58 commit aceeaea
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 54 deletions.
3 changes: 1 addition & 2 deletions packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// imports polyfill from `@next/polyfill-module` after build.
import '../build/polyfills/polyfill-module'
import './components/globals/handle-global-errors'
import ReactDOMClient from 'react-dom/client'
import React, { use } from 'react'
// eslint-disable-next-line import/no-extraneous-dependencies
import { createFromReadableStream } from 'react-server-dom-webpack/client'

import { HeadManagerContext } from '../shared/lib/head-manager-context.shared-runtime'
import { onRecoverableError } from './on-recoverable-error'
import { callServer } from './app-call-server'
Expand All @@ -19,7 +19,6 @@ import type { InitialRSCPayload } from '../server/app-render/types'
import { createInitialRouterState } from './components/router-reducer/create-initial-router-state'
import { MissingSlotContext } from '../shared/lib/app-router-context.shared-runtime'

// Patch console.error to collect information about hydration errors
const origConsoleError = window.console.error
window.console.error = (...args) => {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { handleGlobalErrors } from '../react-dev-overlay/internal/helpers/use-error-handler'

handleGlobalErrors()
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { useEffect } from 'react'

import { isNextRouterError } from '../../../is-next-router-error'
import { isHydrationError } from '../../../is-hydration-error'
import { attachHydrationErrorState } from './attach-hydration-error-state'
import { isNextRouterError } from '../../../is-next-router-error'

export type ErrorHandler = (error: Error) => void

if (typeof window !== 'undefined') {
try {
// Increase the number of stack frames on the client
Error.stackTraceLimit = 50
} catch {}
}

let hasHydrationError = false
const errorQueue: Array<Error> = []
const rejectionQueue: Array<Error> = []
const errorHandlers: Array<ErrorHandler> = []
const rejectionQueue: Array<Error> = []
const rejectionHandlers: Array<ErrorHandler> = []

export function handleClientError(error: unknown) {
Expand All @@ -38,48 +30,6 @@ export function handleClientError(error: unknown) {
handler(error)
}
}
if (typeof window !== 'undefined') {
// These event handlers must be added outside of the hook because there is no
// guarantee that the hook will be alive in a mounted component in time to
// when the errors occur.
// uncaught errors go through reportError
window.addEventListener(
'error',
(event: WindowEventMap['error']): void | boolean => {
if (isNextRouterError(event.error)) {
event.preventDefault()
return false
}
handleClientError(event.error)
}
)

window.addEventListener(
'unhandledrejection',
(ev: WindowEventMap['unhandledrejection']): void => {
const reason = ev?.reason
if (isNextRouterError(reason)) {
ev.preventDefault()
return
}

if (
!reason ||
!(reason instanceof Error) ||
typeof reason.stack !== 'string'
) {
// A non-error was thrown, we don't have anything to show. :-(
return
}

const e = reason
rejectionQueue.push(e)
for (const handler of rejectionHandlers) {
handler(e)
}
}
)
}

export function useErrorHandler(
handleOnUnhandledError: ErrorHandler,
Expand All @@ -104,3 +54,49 @@ export function useErrorHandler(
}
}, [handleOnUnhandledError, handleOnUnhandledRejection])
}

export function handleGlobalErrors() {
if (typeof window !== 'undefined') {
try {
// Increase the number of stack frames on the client
Error.stackTraceLimit = 50
} catch {}

window.addEventListener(
'error',
(event: WindowEventMap['error']): void | boolean => {
if (isNextRouterError(event.error)) {
event.preventDefault()
return false
}
handleClientError(event.error)
}
)

window.addEventListener(
'unhandledrejection',
(ev: WindowEventMap['unhandledrejection']): void => {
const reason = ev?.reason
if (isNextRouterError(reason)) {
ev.preventDefault()
return
}

if (
!reason ||
!(reason instanceof Error) ||
typeof reason.stack !== 'string'
) {
// A non-error was thrown, we don't have anything to show. :-(
return
}

const e = reason
rejectionQueue.push(e)
for (const handler of rejectionHandlers) {
handler(e)
}
}
)
}
}

0 comments on commit aceeaea

Please sign in to comment.