1 | import React from 'react';
|
2 | import { NativeModules } from 'react-native';
|
3 | import { getAppLoadingLifecycleEmitter } from './AppLoading';
|
4 | const { ExponentAppLoadingManager } = NativeModules;
|
5 | async function finishedAsync() {
|
6 | if (ExponentAppLoadingManager && ExponentAppLoadingManager.finishedAsync) {
|
7 | return await ExponentAppLoadingManager.finishedAsync();
|
8 | }
|
9 | }
|
10 | // Store this outside of the component so it is available inside getDerivedStateFromError
|
11 | let _appLoadingIsMounted;
|
12 | /**
|
13 | * This component is never rendered in production!
|
14 | *
|
15 | * In production the app will just hard crash on errors, unless the developer
|
16 | * decides to handle them by overriding the global error handler and swallowing
|
17 | * the error, in which case they are responsible for determining how to recover
|
18 | * from this state.
|
19 | *
|
20 | * - The sole purpose of this component is to hide the splash screen if an
|
21 | * error occurs that prevents it from being hidden. Please note that this
|
22 | * currently only works with <AppLoading /> and not SplashScreen.preventAutoHide()!
|
23 | * - We only want to update the error state when the splash screen is visible, after
|
24 | * the splash screen is gone we don't want to do anything in this component.
|
25 | * - On Android it is necessary for us to render some content in order to hide
|
26 | * the splash screen, just calling `ExponentAppLoadingManager.finishedAsync()`
|
27 | * is not sufficient.
|
28 | *
|
29 | */
|
30 | export default class RootErrorBoundary extends React.Component {
|
31 | constructor(props) {
|
32 | super(props);
|
33 | this._subscribeToGlobalErrors = () => {
|
34 | _appLoadingIsMounted = true;
|
35 | const originalErrorHandler = ErrorUtils.getGlobalHandler();
|
36 | ErrorUtils.setGlobalHandler((error, isFatal) => {
|
37 | if (_appLoadingIsMounted) {
|
38 | finishedAsync();
|
39 | if (isFatal) {
|
40 | this.setState({ error });
|
41 | }
|
42 | }
|
43 | originalErrorHandler(error, isFatal);
|
44 | });
|
45 | };
|
46 | this._unsubscribeFromGlobalErrors = () => {
|
47 | // We don't remove the global error handler that we set here because it is conceivable that the
|
48 | // user may add error handlers *after* we subscribe, and we don't want to override those, so
|
49 | // instead we just gate the call
|
50 | _appLoadingIsMounted = false;
|
51 | };
|
52 | _appLoadingIsMounted = false;
|
53 | getAppLoadingLifecycleEmitter().once('componentDidMount', this._subscribeToGlobalErrors);
|
54 | getAppLoadingLifecycleEmitter().once('componentWillUnmount', this._unsubscribeFromGlobalErrors);
|
55 | this.state = {
|
56 | error: null,
|
57 | };
|
58 | }
|
59 | /**
|
60 | * Test this by adding `throw new Error('example')` to your root component
|
61 | * when the AppLoading component is rendered.
|
62 | */
|
63 | static getDerivedStateFromError(_error) {
|
64 | if (_appLoadingIsMounted) {
|
65 | return { error: true };
|
66 | }
|
67 | return null;
|
68 | }
|
69 | componentDidCatch(error, _errorInfo) {
|
70 | if (_appLoadingIsMounted) {
|
71 | finishedAsync();
|
72 | }
|
73 | throw error;
|
74 | }
|
75 | render() {
|
76 | if (this.state.error) {
|
77 | return null;
|
78 | }
|
79 | else {
|
80 | return this.props.children;
|
81 | }
|
82 | }
|
83 | }
|
84 | //# sourceMappingURL=RootErrorBoundary.js.map |
\ | No newline at end of file |