about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-15 05:35:45 +0100
committerGitHub <noreply@github.com>2019-03-15 05:35:45 +0100
commitc20d096e6a251e1b84ac47b751d2eb065743dbc2 (patch)
treef3e9fa961ba9170e1a70b3854bbe0711ec560b2f
parentff565524aa6aa35c07557ea02d8d04bb3a9e1a97 (diff)
Show disappointed elephant if web UI crashes (#10275)
* Do not crash the whole UI when loading an invalid column

* Add error boundary component to catch Web UI crashes

* Add stack trace on supported browsers

* Add component stack info, pre-format everything for github

* Make “Reload” a clickable link that calls window.location.reload()

* Remove elephant friend from error boundary, make title stand out more

* Simplify error boundary to only a graphic
-rw-r--r--app/javascript/mastodon/components/error_boundary.js39
-rw-r--r--app/javascript/mastodon/containers/mastodon.js5
-rw-r--r--app/javascript/mastodon/features/ui/components/bundle.js5
3 files changed, 48 insertions, 1 deletions
diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js
new file mode 100644
index 000000000..d1ca5bf75
--- /dev/null
+++ b/app/javascript/mastodon/components/error_boundary.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import illustration from '../../images/elephant_ui_disappointed.svg';
+
+export default class ErrorBoundary extends React.PureComponent {
+
+  static propTypes = {
+    children: PropTypes.node,
+  };
+
+  state = {
+    hasError: false,
+    stackTrace: undefined,
+    componentStack: undefined,
+  }
+
+  componentDidCatch(error, info) {
+    this.setState({
+      hasError: true,
+      stackTrace: error.stack,
+      componentStack: info && info.componentStack,
+    });
+  }
+
+  render() {
+    const { hasError } = this.state;
+
+    if (!hasError) {
+      return this.props.children;
+    }
+
+    return (
+      <div>
+        <img src={illustration} alt='' />
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index 2912540a0..542b68282 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -13,6 +13,7 @@ import { connectUserStream } from '../actions/streaming';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import { getLocale } from '../locales';
 import initialState from '../initial_state';
+import ErrorBoundary from '../components/error_boundary';
 
 const { localeData, messages } = getLocale();
 addLocaleData(localeData);
@@ -75,7 +76,9 @@ export default class Mastodon extends React.PureComponent {
     return (
       <IntlProvider locale={locale} messages={messages}>
         <Provider store={store}>
-          <MastodonMount />
+          <ErrorBoundary>
+            <MastodonMount />
+          </ErrorBoundary>
         </Provider>
       </IntlProvider>
     );
diff --git a/app/javascript/mastodon/features/ui/components/bundle.js b/app/javascript/mastodon/features/ui/components/bundle.js
index e7d935251..a60ace35b 100644
--- a/app/javascript/mastodon/features/ui/components/bundle.js
+++ b/app/javascript/mastodon/features/ui/components/bundle.js
@@ -53,6 +53,11 @@ class Bundle extends React.PureComponent {
     const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
     const cachedMod = Bundle.cache.get(fetchComponent);
 
+    if (fetchComponent === undefined) {
+      this.setState({ mod: null });
+      return Promise.resolve();
+    }
+
     onFetch();
 
     if (cachedMod) {