-
-
Notifications
You must be signed in to change notification settings - Fork 159
Expand file tree
/
Copy pathErrorBoundary.tsx
More file actions
125 lines (107 loc) · 2.71 KB
/
ErrorBoundary.tsx
File metadata and controls
125 lines (107 loc) · 2.71 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { useEffect } from 'react'
import {
ErrorBoundary as ReactErrorBoundary,
FallbackProps,
useErrorHandler as useReactErrorHandler,
ErrorBoundaryPropsWithComponent,
} from 'react-error-boundary'
import { APIError } from './types'
import * as Sentry from '@sentry/react'
const ERROR_MESSAGE_TIMEOUT_IN_MS = 500
type ErrorBoundaryType = Omit<
ErrorBoundaryPropsWithComponent,
'FallbackComponent'
> & { FallbackComponent?: React.ComponentType<FallbackProps> }
export const ErrorMessage = ({
error,
defaultError,
}: {
error: Error | unknown
defaultError: Error
}): null => {
useErrorHandler(error, { defaultError: defaultError })
return null
}
const handleError = (error: Error) => {
if (error.name === 'HandledError') {
return
}
if (error.name === 'AbortError') {
return
}
Sentry.captureException(error)
}
export const ErrorBoundary = ({
children,
FallbackComponent = ErrorFallback,
...props
}: React.PropsWithChildren<ErrorBoundaryType>): JSX.Element => {
return (
<ReactErrorBoundary
FallbackComponent={FallbackComponent}
{...props}
onError={handleError}
>
{children}
</ReactErrorBoundary>
)
}
export const ErrorFallback = ({
error,
resetErrorBoundary,
}: FallbackProps): JSX.Element => {
useEffect(() => {
const timer = setTimeout(resetErrorBoundary, ERROR_MESSAGE_TIMEOUT_IN_MS)
return () => clearTimeout(timer)
}, [resetErrorBoundary])
return (
<div>
<p>{error.message}</p>
</div>
)
}
export const useErrorHandler = (
error: unknown,
{ defaultError }: { defaultError: Error }
): void => {
const handler = useReactErrorHandler()
useEffect(() => {
if (!error) {
return
}
if (error instanceof Error) {
if (error.name === 'AbortError') {
return
}
if (process.env.NODE_ENV == 'production') {
Sentry.captureException(error)
}
handler(new HandledError(defaultError.message))
} else if (error instanceof Response) {
const contentType = error.headers.get('Content-Type')
const isJson =
contentType &&
(contentType.includes('application/json') ||
contentType.includes('+json'))
if (isJson) {
error
.clone()
.json()
.then((res: { error: APIError }) => {
handler(new HandledError(res.error.message))
})
.catch(() => {
handler(new HandledError(defaultError.message))
})
} else {
handler(new HandledError(defaultError.message))
}
}
}, [defaultError, error, handler])
}
export class HandledError extends Error {
constructor(message: string) {
super(message)
this.name = 'HandledError'
}
}