From 0b2bc1c57b48e5817a4b1a4728112ec61d5ec771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 30 Nov 2015 18:23:03 +0100 Subject: [PATCH] Introduce ReactError mixin for handling errors in React components --- js/app.js | 22 +++------- .../ascribe_detail/edition_container.js | 17 +++----- .../ascribe_detail/piece_container.js | 9 ++-- js/components/error_not_found_page.js | 12 +++++- js/mixins/react_error.js | 12 ++++++ js/models/errors.js | 24 +++++++++++ js/react_error.js | 42 ------------------- js/react_error_handlers.js | 5 --- 8 files changed, 64 insertions(+), 79 deletions(-) create mode 100644 js/mixins/react_error.js create mode 100644 js/models/errors.js delete mode 100644 js/react_error.js delete mode 100644 js/react_error_handlers.js diff --git a/js/app.js b/js/app.js index 9e8e3a91..520bedbd 100644 --- a/js/app.js +++ b/js/app.js @@ -5,8 +5,6 @@ require('babel/polyfill'); import React from 'react'; import { Router, Redirect } from 'react-router'; import history from './history'; -import { ReactError, ResourceNotFoundError } from './react_error.js'; -import { ResourceNotFoundErrorHandler } from './react_error_handlers'; /* eslint-disable */ import fetch from 'isomorphic-fetch'; @@ -93,20 +91,12 @@ class AppGateway { // us in that case. history.listen(EventActions.routeDidChange); - ReactError({ - render: React.render, - params: [( - - {redirectRoute} - {getRoutes(type, subdomain)} - - ), - document.getElementById('main')], - errors: [{ - error: ResourceNotFoundError, - handler: ResourceNotFoundErrorHandler - }] - }); + React.render(( + + {redirectRoute} + {getRoutes(type, subdomain)} + + ), document.getElementById('main')); // Send the applicationDidBoot event to the third-party stores EventActions.applicationDidBoot(settings); diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index af7b7928..189f7e60 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -3,6 +3,9 @@ import React from 'react'; import { History } from 'react-router'; +import ReactError from '../../mixins/react_error'; +import { ResourceNotFoundError } from '../../models/errors'; + import EditionActions from '../../actions/edition_actions'; import EditionStore from '../../stores/edition_store'; @@ -10,6 +13,7 @@ import Edition from './edition'; import AscribeSpinner from '../ascribe_spinner'; +import { getLangText } from '../../utils/lang_utils'; import { setDocumentTitle } from '../../utils/dom_utils'; @@ -21,7 +25,7 @@ let EditionContainer = React.createClass({ params: React.PropTypes.object }, - mixins: [History], + mixins: [History, ReactError], getInitialState() { return EditionStore.getState(); @@ -33,14 +37,7 @@ let EditionContainer = React.createClass({ // as it will otherwise display wrong/old data once the user loads // the edition detail a second time EditionActions.updateEdition({}); - EditionStore.listen(this.onChange); - - // Every time we enter the edition detail page, just reset the edition - // store as it will otherwise display wrong/old data once the user loads - // the edition detail a second time - EditionActions.updateEdition({}); - this.loadEdition(); }, @@ -57,9 +54,7 @@ let EditionContainer = React.createClass({ const { editionError } = this.state; if(editionError && editionError.status === 404) { - // Even though the /404 path doesn't really exist, - // we can still redirect there and the page will show up - this.history.pushState(null, '/404'); + this.throws(new ResourceNotFoundError(getLangText('Ups, the edition you\'re looking for doesn\'t exist.'))); } }, diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 187c53c1..5198e6ac 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -4,6 +4,9 @@ import React from 'react'; import { History } from 'react-router'; import Moment from 'moment'; +import ReactError from '../../mixins/react_error'; +import { ResourceNotFoundError } from '../../models/errors'; + import PieceActions from '../../actions/piece_actions'; import PieceStore from '../../stores/piece_store'; @@ -53,7 +56,7 @@ let PieceContainer = React.createClass({ params: React.PropTypes.object }, - mixins: [History], + mixins: [History, ReactError], getInitialState() { return mergeOptions( @@ -91,9 +94,7 @@ let PieceContainer = React.createClass({ const { pieceError } = this.state; if(pieceError && pieceError.status === 404) { - // Even though this path doesn't exist we can redirect - // to it as it catches all unknown paths - this.history.pushState(null, '/404'); + this.throws(new ResourceNotFoundError(getLangText('Ups, the piece you\'re looking for doesn\'t exist.'))); } }, diff --git a/js/components/error_not_found_page.js b/js/components/error_not_found_page.js index 61f83196..670f8629 100644 --- a/js/components/error_not_found_page.js +++ b/js/components/error_not_found_page.js @@ -6,6 +6,16 @@ import { getLangText } from '../utils/lang_utils'; let ErrorNotFoundPage = React.createClass({ + propTypes: { + message: React.PropTypes.string + }, + + getDefaultProps() { + return { + message: getLangText('Ups, the page you are looking for does not exist.') + }; + }, + render() { return (
@@ -13,7 +23,7 @@ let ErrorNotFoundPage = React.createClass({

404

- {getLangText('Ups, the page you are looking for does not exist.')} + {this.props.message}

diff --git a/js/mixins/react_error.js b/js/mixins/react_error.js new file mode 100644 index 00000000..0b7669de --- /dev/null +++ b/js/mixins/react_error.js @@ -0,0 +1,12 @@ +'use strict'; + +import invariant from 'invariant'; + +const ReactError = { + throws(err) { + invariant(err.handler, 'You need to specify a `handler` for this error'); + err.handler(this, err); + } +}; + +export default ReactError; diff --git a/js/models/errors.js b/js/models/errors.js new file mode 100644 index 00000000..68d97c86 --- /dev/null +++ b/js/models/errors.js @@ -0,0 +1,24 @@ +'use strict'; + +import React from 'react'; + +import ErrorNotFoundPage from '../components/error_not_found_page'; + + +export class ResourceNotFoundError extends Error { + constructor(message) { + super(message); + this.name = this.constructor.name; + this.message = message; + Error.captureStackTrace(this, this.constructor.name); + } + + handler(component, err) { + if(!component.state._monkeyPatched) { + component.render = () => ; + component.setState({ + _monkeyPatched: true + }); + } + } +} diff --git a/js/react_error.js b/js/react_error.js deleted file mode 100644 index 61fb4a70..00000000 --- a/js/react_error.js +++ /dev/null @@ -1,42 +0,0 @@ -'use strict'; - -function _transformErrorsListToHashMap(errors) { - return errors.reduce((errObj, { error, handler }) => { - const errorName = error.name; - if(!errObj[errorName]) { - errObj[errorName] = { - error: error, - handler: handler - }; - return errObj; - } else { - throw new Error('You\'re trying to override the error handler for ' + errorName + ' by specifying it twice.'); - } - }, {}); -} - -export function ReactError({ render, params, errors }) { - errors = _transformErrorsListToHashMap(errors); - try { - // use react's render function + parameters to render - // the application - render(...params); - } catch(err) { - const potErrorRoutine = errors[err.name]; - if(potErrorRoutine) { - potErrorRoutine.handler(err); - } else { - console.error(err.stack); - } - } -} - -// Followed: http://stackoverflow.com/a/32749533/1263876 for extending errors -export class ResourceNotFoundError extends Error { - constructor(message) { - super(message); - this.name = this.constructor.name; - this.message = message; - Error.captureStackTrace(this, this.constructor.name); - } -} \ No newline at end of file diff --git a/js/react_error_handlers.js b/js/react_error_handlers.js deleted file mode 100644 index 57366603..00000000 --- a/js/react_error_handlers.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -export function ResourceNotFoundErrorHandler() { - console.log(arguments); -} \ No newline at end of file