Render ErrorPage component via app-level error boundary.

This commit is contained in:
Douglas Hall
2019-06-06 10:54:15 -04:00
parent 223ea212bc
commit 0d61d09ddd
8 changed files with 67 additions and 63 deletions

View File

@@ -1,5 +1,4 @@
import { put, push, call, takeEvery } from 'redux-saga/effects';
import { logAPIErrorResponse } from '@edx/frontend-logging';
import { put, call, takeEvery } from 'redux-saga/effects';
import {
DELETE_ACCOUNT,
@@ -19,8 +18,7 @@ export function* handleDeleteAccount(action) {
if (typeof e.response.data === 'string') {
yield put(deleteAccountFailure());
} else {
logAPIErrorResponse(e);
yield put(push('/error'));
throw e;
}
}
}

View File

@@ -1,18 +1,12 @@
import { put, call, push, takeEvery } from 'redux-saga/effects';
import { logAPIErrorResponse } from '@edx/frontend-logging';
import { put, call, takeEvery } from 'redux-saga/effects';
import { resetPasswordBegin, resetPasswordSuccess, RESET_PASSWORD } from './actions';
import { postResetPassword } from './service';
function* handleResetPassword(action) {
try {
yield put(resetPasswordBegin());
const response = yield call(postResetPassword, action.payload.email);
yield put(resetPasswordSuccess(response));
} catch (e) {
logAPIErrorResponse(e);
yield put(push('/error'));
}
yield put(resetPasswordBegin());
const response = yield call(postResetPassword, action.payload.email);
yield put(resetPasswordSuccess(response));
}
export default function* saga() {

View File

@@ -1,6 +1,4 @@
import { call, put, delay, takeEvery, select, all } from 'redux-saga/effects';
import { push } from 'connected-react-router';
import { logAPIErrorResponse } from '@edx/frontend-logging';
// Actions
import {
@@ -54,9 +52,8 @@ export function* handleFetchSettings() {
timeZones,
}));
} catch (e) {
logAPIErrorResponse(e);
yield put(fetchSettingsFailure(e.message));
yield put(push('/error'));
throw e;
}
}
@@ -89,21 +86,15 @@ export function* handleSaveSettings(action) {
if (e.fieldErrors) {
yield put(saveSettingsFailure({ fieldErrors: e.fieldErrors }));
} else {
logAPIErrorResponse(e);
yield put(saveSettingsFailure(e.message));
yield put(push('/error'));
throw e;
}
}
}
export function* handleFetchTimeZones(action) {
try {
const response = yield call(ApiService.getTimeZones, action.payload.country);
yield put(fetchTimeZonesSuccess(response, action.payload.country));
} catch (e) {
logAPIErrorResponse(e);
yield put(push('/error'));
}
const response = yield call(ApiService.getTimeZones, action.payload.country);
yield put(fetchTimeZonesSuccess(response, action.payload.country));
}

View File

@@ -1,12 +1,14 @@
import { Component } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { NewRelicLoggingService } from '@edx/frontend-logging';
import { logAPIErrorResponse } from '@edx/frontend-logging';
import ErrorPage from './ErrorPage';
/*
Error boundary component used to log caught errors and reload the page.
Error boundary component used to log caught errors and display the error page.
*/
export default class ReloadOnError extends Component {
export default class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
@@ -18,23 +20,22 @@ export default class ReloadOnError extends Component {
}
componentDidCatch(error, info) {
NewRelicLoggingService.logError(`${error} ${info}`);
logAPIErrorResponse(`${error} ${info}`);
}
render() {
if (this.state.hasError) {
// Reload the page so the user is not stuck with a broken app.
window.location.reload();
return <ErrorPage />;
}
return this.props.children;
}
}
ReloadOnError.propTypes = {
ErrorBoundary.propTypes = {
children: PropTypes.node,
};
ReloadOnError.defaultProps = {
ErrorBoundary.defaultProps = {
children: null,
};

View File

@@ -0,0 +1,42 @@
import React, { Component } from 'react';
import { FormattedMessage } from '@edx/frontend-i18n';
import { Button } from '@edx/paragon';
export default class ErrorPage extends Component {
reload() {
window.location.reload();
}
render() {
return (
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
<div className="row">
<div className="col">
<p className="my-0 py-5 text-muted">
<FormattedMessage
id="unexpected.error.message.text"
defaultMessage="An unexpected error occurred. Please click the button below to return to refresh the page."
description="error message when an unexpected error occurs"
/>
</p>
</div>
</div>
<div className="row">
<div className="col">
<Button
buttonType="primary"
onClick={this.reload}
label={
<FormattedMessage
id="unexpected.error.button.text"
defaultMessage="Try Again"
description="text for button that tries to reload the app by refreshing the page"
/>
}
/>
</div>
</div>
</div>
);
}
}

View File

@@ -1,14 +1,14 @@
import * as utils from './utils';
import Alert from './components/Alert';
import PageLoading from './components/PageLoading';
import ReloadOnError from './components/ReloadOnError';
import ErrorBoundary from './components/ErrorBoundary';
import SwitchContent from './components/SwitchContent';
import { configureUserAccountApiService, fetchUserAccount } from './actions';
export {
Alert,
ErrorBoundary,
PageLoading,
ReloadOnError,
SwitchContent,
utils,
configureUserAccountApiService,

View File

@@ -17,12 +17,11 @@ import {
} from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { ReloadOnError, fetchUserAccount } from '../common';
import { ErrorBoundary, fetchUserAccount } from '../common';
import { ConnectedAccountSettingsPage } from '../account-settings';
import FooterLogo from '../assets/edx-footer.png';
import HeaderLogo from '../assets/logo.svg';
import ErrorPage from './ErrorPage';
import NotFoundPage from './NotFoundPage';
import messages from './App.messages';
@@ -138,7 +137,6 @@ function PageContent({
<main>
<Switch>
<Route exact path="/" component={ConnectedAccountSettingsPage} />
<Route path="/error" component={ErrorPage} />
<Route path="/notfound" component={NotFoundPage} />
<Route path="*" component={NotFoundPage} />
</Switch>
@@ -179,7 +177,7 @@ class App extends Component {
render() {
return (
<ReloadOnError>
<ErrorBoundary>
<IntlProvider locale={this.props.locale} messages={getMessages()}>
<Provider store={this.props.store}>
<ConnectedRouter history={this.props.history}>
@@ -191,7 +189,7 @@ class App extends Component {
</ConnectedRouter>
</Provider>
</IntlProvider>
</ReloadOnError>
</ErrorBoundary>
);
}
}

View File

@@ -1,20 +0,0 @@
import React from 'react';
import { FormattedMessage } from '@edx/frontend-i18n';
export default function ErrorPage() {
return (
<div className="container-fluid py-5 justify-content-center align-items-start text-center">
<div className="row">
<div className="col">
<p className="my-0 py-5 text-muted">
<FormattedMessage
id="error.unexpected.message"
defaultMessage="An unexpected error occurred."
description="error message when an unexpected error occurs"
/>
</p>
</div>
</div>
</div>
);
}