-
Notifications
You must be signed in to change notification settings - Fork 7
/
index.tsx
102 lines (89 loc) · 2.55 KB
/
index.tsx
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
import React, {
Component,
useState,
useCallback,
ComponentLifecycle,
FC,
createContext,
useContext,
MutableRefObject,
useMemo,
useRef,
ComponentType,
} from "react";
// eslint-disable-next-line @typescript-eslint/ban-types
type ComponentDidCatch = ComponentLifecycle<{}, {}>["componentDidCatch"];
interface ErrorBoundaryProps {
error: unknown;
onError: NonNullable<ComponentDidCatch>;
}
class ErrorBoundary extends Component<ErrorBoundaryProps> {
displayName = "ReactUseErrorBoundary";
componentDidCatch(...args: Parameters<NonNullable<ComponentDidCatch>>) {
// silence React warning:
// ErrorBoundary: Error boundaries should implement getDerivedStateFromError(). In that method, return a state update to display an error message or fallback UI
this.setState({});
this.props.onError(...args);
}
render() {
return this.props.children;
}
}
const noop = () => false;
interface ErrorBoundaryCtx {
componentDidCatch: MutableRefObject<ComponentDidCatch>;
error: boolean;
setError: (error: boolean) => void;
}
const errorBoundaryContext = createContext<ErrorBoundaryCtx>({
componentDidCatch: { current: undefined },
error: false,
setError: noop,
});
export const ErrorBoundaryContext: FC = ({ children }) => {
const [error, setError] = useState(false);
const componentDidCatch = useRef<ComponentDidCatch>();
const ctx = useMemo(
() => ({
componentDidCatch,
error,
setError,
}),
[error]
);
return (
<errorBoundaryContext.Provider value={ctx}>
<ErrorBoundary
error={error}
onError={(...args) => {
setError(true);
componentDidCatch.current?.(...args);
}}
>
{children}
</ErrorBoundary>
</errorBoundaryContext.Provider>
);
};
ErrorBoundaryContext.displayName = "ReactUseErrorBoundaryContext";
export function withErrorBoundary<Props = Record<string, unknown>>(
WrappedComponent: ComponentType<Props>
): FC<Props> {
return (props: Props) => (
<ErrorBoundaryContext>
<WrappedComponent key="WrappedComponent" {...props} />
</ErrorBoundaryContext>
);
}
type UseErrorBoundaryReturn = [error: boolean, resetError: () => void];
export function useErrorBoundary(
componentDidCatch?: ComponentDidCatch
): UseErrorBoundaryReturn {
const ctx = useContext(errorBoundaryContext);
ctx.componentDidCatch.current = componentDidCatch;
const resetError = useCallback(() => {
ctx.setError(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return [ctx.error, resetError];
}