/
use-error-handler.ts
106 lines (90 loc) · 2.68 KB
/
use-error-handler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import { useEffect } from 'react'
export type ErrorHandler = (error: Error) => void
export const RuntimeErrorHandler = {
hadRuntimeError: false,
}
function isNextRouterError(error: any): boolean {
return (
error &&
error.digest &&
(error.digest.startsWith('NEXT_REDIRECT') ||
error.digest === 'NEXT_NOT_FOUND')
)
}
function isHydrationError(error: Error): boolean {
return (
error.message.match(/(hydration|content does not match|did not match)/i) !=
null
)
}
try {
Error.stackTraceLimit = 50
} catch {}
const errorQueue: Array<Error> = []
const rejectionQueue: Array<Error> = []
const errorHandlers: Array<ErrorHandler> = []
const rejectionHandlers: Array<ErrorHandler> = []
// 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.
window.addEventListener('error', (ev: WindowEventMap['error']): void => {
if (isNextRouterError(ev.error)) {
ev.preventDefault()
return
}
RuntimeErrorHandler.hadRuntimeError = true
const error = ev?.error
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') {
// A non-error was thrown, we don't have anything to show. :-(
return
}
if (isHydrationError(error)) {
error.message += `\n\nSee more info here: https://nextjs.org/docs/messages/react-hydration-error`
}
const e = error
errorQueue.push(e)
for (const handler of errorHandlers) {
handler(e)
}
})
window.addEventListener(
'unhandledrejection',
(ev: WindowEventMap['unhandledrejection']): void => {
RuntimeErrorHandler.hadRuntimeError = true
const reason = ev?.reason
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,
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])
}