diff --git a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/helpers/use-error-handler.ts b/packages/next/src/client/components/react-dev-overlay/_experimental/internal/helpers/use-error-handler.ts deleted file mode 100644 index 2f75fa8425a6b..0000000000000 --- a/packages/next/src/client/components/react-dev-overlay/_experimental/internal/helpers/use-error-handler.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { useEffect } from 'react' -import { attachHydrationErrorState } from './attach-hydration-error-state' -import { isNextRouterError } from '../../../../is-next-router-error' -import { storeHydrationErrorStateFromConsoleArgs } from './hydration-error-info' -import { formatConsoleArgs } from '../../../../../lib/console' -import isError from '../../../../../../lib/is-error' -import { createUnhandledError } from './console-error' -import { enqueueConsecutiveDedupedError } from './enqueue-client-error' -import { getReactStitchedError } from './stitched-error' - -const queueMicroTask = - globalThis.queueMicrotask || ((cb: () => void) => Promise.resolve().then(cb)) - -export type ErrorHandler = (error: Error) => void - -const errorQueue: Array = [] -const errorHandlers: Array = [] -const rejectionQueue: Array = [] -const rejectionHandlers: Array = [] - -export function handleClientError( - originError: unknown, - consoleErrorArgs: any[], - capturedFromConsole: boolean = false -) { - let error: Error - if (!originError || !isError(originError)) { - // If it's not an error, format the args into an error - const formattedErrorMessage = formatConsoleArgs(consoleErrorArgs) - error = createUnhandledError(formattedErrorMessage) - } else { - error = capturedFromConsole - ? createUnhandledError(originError) - : originError - } - error = getReactStitchedError(error) - - storeHydrationErrorStateFromConsoleArgs(...consoleErrorArgs) - attachHydrationErrorState(error) - - enqueueConsecutiveDedupedError(errorQueue, error) - for (const handler of errorHandlers) { - // Delayed the error being passed to React Dev Overlay, - // avoid the state being synchronously updated in the component. - queueMicroTask(() => { - handler(error) - }) - } -} - -export function useErrorHandler( - handleOnUnhandledError: ErrorHandler, - handleOnUnhandledRejection: ErrorHandler -) { - useEffect(() => { - // Handle queued errors. - errorQueue.forEach(handleOnUnhandledError) - rejectionQueue.forEach(handleOnUnhandledRejection) - - // Listen to new errors. - errorHandlers.push(handleOnUnhandledError) - rejectionHandlers.push(handleOnUnhandledRejection) - - return () => { - // Remove listeners. - errorHandlers.splice(errorHandlers.indexOf(handleOnUnhandledError), 1) - rejectionHandlers.splice( - rejectionHandlers.indexOf(handleOnUnhandledRejection), - 1 - ) - } - }, [handleOnUnhandledError, handleOnUnhandledRejection]) -} - -function onUnhandledError(event: WindowEventMap['error']): void | boolean { - if (isNextRouterError(event.error)) { - event.preventDefault() - return false - } - handleClientError(event.error, []) -} - -function onUnhandledRejection(ev: WindowEventMap['unhandledrejection']): void { - const reason = ev?.reason - if (isNextRouterError(reason)) { - ev.preventDefault() - return - } - - let error = reason - if (error && !isError(error)) { - error = createUnhandledError(error + '') - } - - rejectionQueue.push(error) - for (const handler of rejectionHandlers) { - handler(error) - } -} - -export function handleGlobalErrors() { - if (typeof window !== 'undefined') { - try { - // Increase the number of stack frames on the client - Error.stackTraceLimit = 50 - } catch {} - - window.addEventListener('error', onUnhandledError) - window.addEventListener('unhandledrejection', onUnhandledRejection) - } -} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts index 3fdfd27831179..a7ad315cc6bed 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts +++ b/packages/next/src/client/components/react-dev-overlay/internal/helpers/use-error-handler.ts @@ -77,7 +77,11 @@ function onUnhandledError(event: WindowEventMap['error']): void | boolean { event.preventDefault() return false } - handleClientError(event.error, []) + // When there's an error property present, we log the error to error overlay. + // Otherwise we don't do anything as it's not logging in the console either. + if (event.error) { + handleClientError(event.error, []) + } } function onUnhandledRejection(ev: WindowEventMap['unhandledrejection']): void { diff --git a/test/development/app-dir/capture-console-error/app/browser/error-event/page.js b/test/development/app-dir/capture-console-error/app/browser/error-event/page.js new file mode 100644 index 0000000000000..345a48878a53c --- /dev/null +++ b/test/development/app-dir/capture-console-error/app/browser/error-event/page.js @@ -0,0 +1,20 @@ +'use client' + +export default function Page() { + return ( + + ) +} diff --git a/test/development/app-dir/capture-console-error/capture-console-error.test.ts b/test/development/app-dir/capture-console-error/capture-console-error.test.ts index bd22616d99a71..35c6cc68462e0 100644 --- a/test/development/app-dir/capture-console-error/capture-console-error.test.ts +++ b/test/development/app-dir/capture-console-error/capture-console-error.test.ts @@ -7,6 +7,8 @@ import { getRedboxTotalErrorCount, openRedbox, hasRedboxCallStack, + assertNoRedbox, + assertNoConsoleErrors, } from 'next-test-utils' async function getRedboxResult(browser: any) { @@ -315,4 +317,12 @@ describe('app-dir - capture-console-error', () => { `) } }) + + it('should display the error message in error event when event.error is not present', async () => { + const browser = await next.browser('/browser/error-event') + await browser.elementByCss('button').click() + + await assertNoRedbox(browser) + await assertNoConsoleErrors(browser) + }) }) diff --git a/test/lib/next-test-utils.ts b/test/lib/next-test-utils.ts index f0800cb6a669d..d4993ae128c66 100644 --- a/test/lib/next-test-utils.ts +++ b/test/lib/next-test-utils.ts @@ -1545,3 +1545,18 @@ export function createNowRouteMatches( return urlSearchParams } + +export async function assertNoConsoleErrors(browser: BrowserInterface) { + const logs = await browser.log() + const warningsAndErrors = logs.filter((log) => { + return ( + log.source === 'warning' || + (log.source === 'error' && + // These are expected when we visit 404 pages. + log.message !== + 'Failed to load resource: the server responded with a status of 404 (Not Found)') + ) + }) + + expect(warningsAndErrors).toEqual([]) +}