Просмотр исходного кода

Merge pull request #2193 from weseek/support/apply-error-boundary

Support/apply error boundary
Yuki Takei 5 лет назад
Родитель
Сommit
f5eb8784db
3 измененных файлов с 79 добавлено и 12 удалено
  1. 14 8
      src/client/js/admin.jsx
  2. 9 4
      src/client/js/app.jsx
  3. 56 0
      src/client/js/components/ErrorBoudary.jsx

+ 14 - 8
src/client/js/admin.jsx

@@ -5,6 +5,8 @@ import { I18nextProvider } from 'react-i18next';
 
 
 import loggerFactory from '@alias/logger';
 import loggerFactory from '@alias/logger';
 
 
+import ErrorBoundary from './components/ErrorBoudary';
+
 import AdminHome from './components/Admin/AdminHome/AdminHome';
 import AdminHome from './components/Admin/AdminHome/AdminHome';
 import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
 import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
 import NotificationSetting from './components/Admin/Notification/NotificationSetting';
 import NotificationSetting from './components/Admin/Notification/NotificationSetting';
@@ -99,9 +101,11 @@ Object.keys(componentMappings).forEach((key) => {
   if (elem) {
   if (elem) {
     ReactDOM.render(
     ReactDOM.render(
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
-        <Provider inject={injectableContainers}>
-          {componentMappings[key]}
-        </Provider>
+        <ErrorBoundary>
+          <Provider inject={injectableContainers}>
+            {componentMappings[key]}
+          </Provider>
+        </ErrorBoundary>
       </I18nextProvider>,
       </I18nextProvider>,
       elem,
       elem,
     );
     );
@@ -124,11 +128,13 @@ if (adminSecuritySettingElem != null) {
     adminOidcSecurityContainer, adminBasicSecurityContainer, adminGoogleSecurityContainer, adminGitHubSecurityContainer, adminTwitterSecurityContainer,
     adminOidcSecurityContainer, adminBasicSecurityContainer, adminGoogleSecurityContainer, adminGitHubSecurityContainer, adminTwitterSecurityContainer,
   ];
   ];
   ReactDOM.render(
   ReactDOM.render(
-    <Provider inject={[...injectableContainers, ...adminSecurityContainers]}>
-      <I18nextProvider i18n={i18n}>
-        <SecurityManagement />
-      </I18nextProvider>
-    </Provider>,
+    <I18nextProvider i18n={i18n}>
+      <ErrorBoundary>
+        <Provider inject={[...injectableContainers, ...adminSecurityContainers]}>
+          <SecurityManagement />
+        </Provider>
+      </ErrorBoundary>
+    </I18nextProvider>,
     adminSecuritySettingElem,
     adminSecuritySettingElem,
   );
   );
 }
 }

+ 9 - 4
src/client/js/app.jsx

@@ -5,6 +5,7 @@ import { I18nextProvider } from 'react-i18next';
 
 
 import loggerFactory from '@alias/logger';
 import loggerFactory from '@alias/logger';
 
 
+import ErrorBoundary from './components/ErrorBoudary';
 import SearchPage from './components/SearchPage';
 import SearchPage from './components/SearchPage';
 import TagsList from './components/TagsList';
 import TagsList from './components/TagsList';
 import PageEditor from './components/PageEditor';
 import PageEditor from './components/PageEditor';
@@ -111,9 +112,11 @@ Object.keys(componentMappings).forEach((key) => {
   if (elem) {
   if (elem) {
     ReactDOM.render(
     ReactDOM.render(
       <I18nextProvider i18n={i18n}>
       <I18nextProvider i18n={i18n}>
-        <Provider inject={injectableContainers}>
-          {componentMappings[key]}
-        </Provider>
+        <ErrorBoundary>
+          <Provider inject={injectableContainers}>
+            {componentMappings[key]}
+          </Provider>
+        </ErrorBoundary>
       </I18nextProvider>,
       </I18nextProvider>,
       elem,
       elem,
     );
     );
@@ -124,7 +127,9 @@ Object.keys(componentMappings).forEach((key) => {
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
   ReactDOM.render(
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
     <I18nextProvider i18n={i18n}>
-      <PageHistory pageId={pageContainer.state.pageId} crowi={appContainer} />
+      <ErrorBoundary>
+        <PageHistory pageId={pageContainer.state.pageId} crowi={appContainer} />
+      </ErrorBoundary>
     </I18nextProvider>, document.getElementById('revision-history'),
     </I18nextProvider>, document.getElementById('revision-history'),
   );
   );
 });
 });

+ 56 - 0
src/client/js/components/ErrorBoudary.jsx

@@ -0,0 +1,56 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+/**
+ * @see https://reactjs.org/docs/error-boundaries.html
+ */
+class ErrorBoundary extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = { error: null, errorInfo: null };
+  }
+
+  componentDidCatch(error, errorInfo) {
+    // You can also log the error to an error reporting service
+    // logErrorToMyService(error, errorInfo);
+
+    // Catch errors in any components below and re-render with error message
+    this.setState({
+      error,
+      errorInfo,
+    });
+  }
+
+  render() {
+    const { error, errorInfo } = this.state;
+    if (errorInfo != null) {
+
+      // split componetStack
+      // see https://regex101.com/r/Uc448G/1
+      const firstStack = errorInfo.componentStack.split(/\s*in\s/)[1];
+
+      return (
+        <div className="card border-danger">
+          <div className="card-header">Error occured in {firstStack}</div>
+          <div className="card-body">
+            <h5>{error && error.toString()}</h5>
+            <details className="card well small mb-0" style={{ whiteSpace: 'pre-wrap' }}>
+              {errorInfo.componentStack}
+            </details>
+          </div>
+        </div>
+      );
+    }
+
+    // Normally, just render children
+    return this.props.children;
+  }
+
+}
+
+ErrorBoundary.propTypes = {
+  children: PropTypes.object,
+};
+
+export default ErrorBoundary;