From 7f1dc831265e0ba4f7f13a0e307daf28d91f8a90 Mon Sep 17 00:00:00 2001 From: Noah Loomans Date: Fri, 6 Jul 2018 21:01:51 +0200 Subject: client/schedule: Properly handle errors --- src/client/react/components/container/View.js | 12 +++++- .../react/components/presentational/Schedule.js | 7 +++- .../presentational/ScheduleErrorDisplay.js | 46 ++++++++++++++++++++++ .../presentational/ScheduleErrorDisplay.scss | 4 ++ src/client/react/lib/rejectIfBadStatus.js | 1 + src/client/react/store/actions.js | 12 +++--- src/client/react/store/reducers.js | 17 ++++++-- 7 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/client/react/components/presentational/ScheduleErrorDisplay.js create mode 100644 src/client/react/components/presentational/ScheduleErrorDisplay.scss create mode 100644 src/client/react/lib/rejectIfBadStatus.js diff --git a/src/client/react/components/container/View.js b/src/client/react/components/container/View.js index a601226..68bdde9 100644 --- a/src/client/react/components/container/View.js +++ b/src/client/react/components/container/View.js @@ -26,6 +26,7 @@ import extractSchedule from '../../lib/extractSchedule'; import Schedule from '../presentational/Schedule'; import Loading from '../presentational/Loading'; +import ScheduleErrorDisplay from '../presentational/ScheduleErrorDisplay'; import * as actions from '../../store/actions'; class View extends React.Component { @@ -45,6 +46,14 @@ class View extends React.Component { fetchScheduleIfNeeded(user, week); } + componentDidUpdate(prevProps) { + const { fetchScheduleIfNeeded, user, week } = this.props; + + if (prevProps.user !== user || prevProps.week !== week) { + fetchScheduleIfNeeded(user, week); + } + } + render() { const { user, @@ -60,6 +69,8 @@ class View extends React.Component { return ; case 'FINISHED': return ; + case 'ERROR': + return ; default: throw new Error(`${schedule.state} is not a valid schedule state.`); } @@ -70,7 +81,6 @@ const mapStateToProps = (state) => { const user = selectUser(state); const week = selectWeek(state); return { - key: `${user}:${week}`, user, week, schedules: state.schedules, diff --git a/src/client/react/components/presentational/Schedule.js b/src/client/react/components/presentational/Schedule.js index 727a1f4..9791658 100644 --- a/src/client/react/components/presentational/Schedule.js +++ b/src/client/react/components/presentational/Schedule.js @@ -38,8 +38,11 @@ class Schedule extends React.Component { this.updateScaling(); } - componentDidUpdate() { - this.updateScaling(); + componentDidUpdate(prevProps) { + const { htmlStr } = this.props; + if (prevProps.htmlStr !== htmlStr) { + this.updateScaling(); + } } componentWillUnmount() { diff --git a/src/client/react/components/presentational/ScheduleErrorDisplay.js b/src/client/react/components/presentational/ScheduleErrorDisplay.js new file mode 100644 index 0000000..59d1f69 --- /dev/null +++ b/src/client/react/components/presentational/ScheduleErrorDisplay.js @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2018 Noah Loomans + * + * This file is part of rooster.hetmml.nl. + * + * rooster.hetmml.nl is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * rooster.hetmml.nl is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with rooster.hetmml.nl. If not, see . + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import './ScheduleErrorDisplay.scss'; + +class ScheduleErrorDisplay extends React.Component { + static propTypes = { + statusCode: PropTypes.string.isRequired, + }; + + render() { + const { statusCode } = this.props; + const errorMessage = ( + statusCode === 404 + ? 'Sorry, er is (nog) geen rooster voor deze week.' + : 'Sorry, er is iets mis gegaan tijdens het laden van deze week.' + ); + + return ( +
+ {errorMessage} +
+ ); + } +} + +export default ScheduleErrorDisplay; diff --git a/src/client/react/components/presentational/ScheduleErrorDisplay.scss b/src/client/react/components/presentational/ScheduleErrorDisplay.scss new file mode 100644 index 0000000..15b9c2a --- /dev/null +++ b/src/client/react/components/presentational/ScheduleErrorDisplay.scss @@ -0,0 +1,4 @@ +.ScheduleErrorDisplay { + margin: 64px 32px; + text-align: center; +} diff --git a/src/client/react/lib/rejectIfBadStatus.js b/src/client/react/lib/rejectIfBadStatus.js new file mode 100644 index 0000000..29bf3b2 --- /dev/null +++ b/src/client/react/lib/rejectIfBadStatus.js @@ -0,0 +1 @@ +export default response => (response.ok ? response : Promise.reject(response.status)); diff --git a/src/client/react/store/actions.js b/src/client/react/store/actions.js index 2157030..131bcaa 100644 --- a/src/client/react/store/actions.js +++ b/src/client/react/store/actions.js @@ -5,6 +5,7 @@ import { selectUser, selectWeek, selectCurrentWeek } from './selectors'; import purifyWeek from '../lib/purifyWeek'; import withinRange from '../lib/withinRange'; import extractSchedule from '../lib/extractSchedule'; +import rejectIfBadStatus from '../lib/rejectIfBadStatus'; function updatePathname(pathname = '') { return (dispatch, getState) => { @@ -93,8 +94,8 @@ const fetchScheduleSuccess = (user, week, htmlStr) => ({ type: 'VIEW/FETCH_SCHEDULE_SUCCESS', user, week, htmlStr, }); -const fetchScheduleError = (user, week) => ({ - type: 'VIEW/FETCH_SCHEDULE_ERROR', user, week, +const fetchScheduleError = (user, week, statusCode) => ({ + type: 'VIEW/FETCH_SCHEDULE_ERROR', user, week, statusCode, }); @@ -110,17 +111,18 @@ export function fetchScheduleIfNeeded(user, week) { dispatch(fetchScheduleStart(user, week)); fetch(`/get/${user}?week=${week}`) + .then(rejectIfBadStatus) .then(r => r.text()) .then( - // success + // success (htmlStr) => { dispatch(fetchScheduleSuccess(user, week, htmlStr)); }, // error - () => { + (statusCode) => { // TODO: Handle error status - dispatch(fetchScheduleError(user, week)); + dispatch(fetchScheduleError(user, week, statusCode)); }, ); }; diff --git a/src/client/react/store/reducers.js b/src/client/react/store/reducers.js index 3e39096..b1af46f 100644 --- a/src/client/react/store/reducers.js +++ b/src/client/react/store/reducers.js @@ -30,17 +30,25 @@ const DEFAULT_STATE = { const schedule = (state = {}, action) => { switch (action.type) { - case 'VIEW/FETCH_SCHEDULE_START': + case 'VIEW/FETCH_SCHEDULE_START': { return { - ...state, state: 'FETCHING', }; - case 'VIEW/FETCH_SCHEDULE_SUCCESS': + } + case 'VIEW/FETCH_SCHEDULE_SUCCESS': { return { - ...state, state: 'FINISHED', htmlStr: action.htmlStr, }; + } + case 'VIEW/FETCH_SCHEDULE_ERROR': { + const { statusCode } = action; + + return { + state: 'ERROR', + statusCode, + }; + } default: return state; } @@ -105,6 +113,7 @@ function reducer(state = DEFAULT_STATE, action) { case 'VIEW/FETCH_SCHEDULE_START': case 'VIEW/FETCH_SCHEDULE_SUCCESS': + case 'VIEW/FETCH_SCHEDULE_ERROR': return { ...state, schedules: { -- cgit v1.1