Some updates to the redux structure

1. split `auth.js` up so it's 1 reducer per file
2. consolidate the conference schedule reducers
3. make conference reducer names more descriptive
4. add error handling for `body === 'undefined'`

Thanks @rschamp for all the help!
This commit is contained in:
Matthew Taylor 2016-05-19 16:55:25 -04:00
parent bca90bd835
commit a860b27941
11 changed files with 109 additions and 131 deletions

View file

@ -2,7 +2,7 @@ var connect = require('react-redux').connect;
var omit = require('lodash.omit'); var omit = require('lodash.omit');
var React = require('react'); var React = require('react');
var actions = require('../../redux/auth.js'); var sessionActions = require('../../redux/session.js');
var Modal = require('../modal/modal.jsx'); var Modal = require('../modal/modal.jsx');
var Registration = require('../registration/registration.jsx'); var Registration = require('../registration/registration.jsx');
@ -48,7 +48,7 @@ var Intro = React.createClass({
this.setState({'registrationOpen': false}); this.setState({'registrationOpen': false});
}, },
completeRegistration: function () { completeRegistration: function () {
this.props.dispatch(actions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.closeRegistration();
}, },
render: function () { render: function () {

View file

@ -5,7 +5,7 @@ var ReactIntl = require('react-intl');
var FormattedMessage = ReactIntl.FormattedMessage; var FormattedMessage = ReactIntl.FormattedMessage;
var injectIntl = ReactIntl.injectIntl; var injectIntl = ReactIntl.injectIntl;
var actions = require('../../../redux/auth.js'); var sessionActions = require('../../../redux/session.js');
var Api = require('../../../mixins/api.jsx'); var Api = require('../../../mixins/api.jsx');
var Avatar = require('../../avatar/avatar.jsx'); var Avatar = require('../../avatar/avatar.jsx');
@ -132,7 +132,7 @@ var Navigation = React.createClass({
this.showCanceledDeletion(); this.showCanceledDeletion();
} }
}.bind(this)); }.bind(this));
this.props.dispatch(actions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
} }
} }
// JS error already logged by api mixin // JS error already logged by api mixin
@ -149,7 +149,7 @@ var Navigation = React.createClass({
}, function (err) { }, function (err) {
if (err) log.error(err); if (err) log.error(err);
this.closeLogin(); this.closeLogin();
this.props.dispatch(actions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
}.bind(this)); }.bind(this));
}, },
handleAccountNavClick: function (e) { handleAccountNavClick: function (e) {
@ -169,7 +169,7 @@ var Navigation = React.createClass({
this.setState({'registrationOpen': false}); this.setState({'registrationOpen': false});
}, },
completeRegistration: function () { completeRegistration: function () {
this.props.dispatch(actions.refreshSession()); this.props.dispatch(sessionActions.refreshSession());
this.closeRegistration(); this.closeRegistration();
}, },
render: function () { render: function () {

View file

@ -6,7 +6,7 @@ var ReactDOM = require('react-dom');
var StoreProvider = require('react-redux').Provider; var StoreProvider = require('react-redux').Provider;
var IntlProvider = require('./intl.jsx').IntlProvider; var IntlProvider = require('./intl.jsx').IntlProvider;
var authActions = require('../redux/auth.js'); var sessionActions = require('../redux/session.js');
var reducer = require('../redux/reducer.js'); var reducer = require('../redux/reducer.js');
require('../main.scss'); require('../main.scss');
@ -43,7 +43,7 @@ var render = function (jsx, element) {
); );
// Get initial session // Get initial session
store.dispatch(authActions.refreshSession()); store.dispatch(sessionActions.refreshSession());
}; };
module.exports = render; module.exports = render;

View file

@ -74,6 +74,9 @@ module.exports.getDetails = function (id) {
dispatch(module.exports.setDetailsError('Not Found')); dispatch(module.exports.setDetailsError('Not Found'));
} }
return; return;
} else {
dispatch(module.exports.setDetailsError('An unexpected error occurred'));
return;
} }
}); });
}; };

View file

@ -2,30 +2,17 @@ var keyMirror = require('keymirror');
var api = require('../mixins/api.jsx').api; var api = require('../mixins/api.jsx').api;
var Types = keyMirror({ var Types = keyMirror({
SET_DAY: null,
SET_SCHEDULE: null, SET_SCHEDULE: null,
SET_SCHEDULE_FETCHING: null, SET_SCHEDULE_FETCHING: null,
SET_DAY_ERROR: null,
SET_SCHEDULE_ERROR: null SET_SCHEDULE_ERROR: null
}); });
module.exports.dayReducer = function (state, action) {
if (typeof state === 'undefined') {
state = '';
}
switch (action.type) {
case Types.SET_DAY:
return action.day;
case Types.SET_DAY_ERROR:
return state;
default:
return state;
}
};
module.exports.scheduleReducer = function (state, action) { module.exports.scheduleReducer = function (state, action) {
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
state = []; state = {
chunks: [],
day: ''
};
} }
switch (action.type) { switch (action.type) {
case Types.SET_SCHEDULE: case Types.SET_SCHEDULE:
@ -39,20 +26,6 @@ module.exports.scheduleReducer = function (state, action) {
} }
}; };
module.exports.setDayError = function (error) {
return {
type: Types.SET_DAY_ERROR,
error: error
};
};
module.exports.setDay = function (day) {
return {
type: Types.SET_DAY,
day: day
};
};
module.exports.setSchedule = function (schedule) { module.exports.setSchedule = function (schedule) {
return { return {
type: Types.SET_SCHEDULE, type: Types.SET_SCHEDULE,
@ -93,7 +66,6 @@ module.exports.getDaySchedule = function (day) {
uri: '/conference/schedule/' + day uri: '/conference/schedule/' + day
}, function (err, body) { }, function (err, body) {
if (err) { if (err) {
dispatch(module.exports.setDayError(err));
dispatch(module.exports.setScheduleError(err)); dispatch(module.exports.setScheduleError(err));
return; return;
} }
@ -150,8 +122,13 @@ module.exports.getDaySchedule = function (day) {
items: scheduleByChunk.chunks[scheduleByChunk.info[i].name] items: scheduleByChunk.chunks[scheduleByChunk.info[i].name]
}); });
} }
dispatch(module.exports.setDay(day)); dispatch(module.exports.setSchedule({
dispatch(module.exports.setSchedule(schedule)); chunks: schedule,
day: day
}));
return;
} else {
dispatch(module.exports.setScheduleError('An unexpected error occurred'));
return; return;
} }
}); });

View file

@ -1,15 +1,15 @@
var combineReducers = require('redux').combineReducers; var combineReducers = require('redux').combineReducers;
var authReducers = require('./auth.js'); var scheduleReducer = require('./conference-schedule.js').scheduleReducer;
var conferenceScheduleReducers = require('./conference-schedule.js'); var detailsReducer = require('./conference-details.js').detailsReducer;
var conferenceDetailsReducers = require('./conference-details.js'); var sessionReducer = require('./session.js').sessionReducer;
var tokenReducer = require('./token.js').tokenReducer;
var appReducer = combineReducers({ var appReducer = combineReducers({
session: authReducers.sessionReducer, session: sessionReducer,
token: authReducers.tokenReducer, token: tokenReducer,
day: conferenceScheduleReducers.dayReducer, conferenceSchedule: scheduleReducer,
schedule: conferenceScheduleReducers.scheduleReducer, conferenceDetails: detailsReducer
details: conferenceDetailsReducers.detailsReducer
}); });
module.exports = appReducer; module.exports = appReducer;

58
src/redux/session.js Normal file
View file

@ -0,0 +1,58 @@
var keyMirror = require('keymirror');
var api = require('../mixins/api.jsx').api;
var Types = keyMirror({
SET_SESSION: null,
SET_SESSION_ERROR: null
});
module.exports.sessionReducer = function (state, action) {
// Reducer for handling changes to session state
if (typeof state === 'undefined') {
state = {};
}
switch (action.type) {
case Types.SET_SESSION:
return action.session;
case Types.SET_SESSION_ERROR:
// TODO: do something with action.error
return state;
default:
return state;
}
};
module.exports.setSessionError = function (error) {
return {
type: Types.SET_SESSION_ERROR,
error: error
};
};
module.exports.setSession = function (session) {
return {
type: Types.SET_SESSION,
session: session
};
};
module.exports.refreshSession = function () {
return function (dispatch) {
api({
host: '',
uri: '/session/'
}, function (err, body) {
if (err) return dispatch(module.exports.setSessionError(err));
if (typeof body !== 'undefined') {
if (body.banned) {
return window.location = body.url;
} else {
dispatch(module.exports.getToken());
dispatch(module.exports.setSession(body));
return;
}
}
});
};
};

View file

@ -1,31 +1,12 @@
var keyMirror = require('keymirror'); var keyMirror = require('keymirror');
var api = require('../mixins/api.jsx').api;
var jar = require('../lib/jar.js'); var jar = require('../lib/jar.js');
var Types = keyMirror({ var Types = keyMirror({
SET_SESSION: null,
SET_SESSION_ERROR: null,
SET_TOKEN: null, SET_TOKEN: null,
SET_TOKEN_ERROR: null, SET_TOKEN_ERROR: null,
USE_TOKEN: null USE_TOKEN: null
}); });
module.exports.sessionReducer = function (state, action) {
// Reducer for handling changes to session state
if (typeof state === 'undefined') {
state = {};
}
switch (action.type) {
case Types.SET_SESSION:
return action.session;
case Types.SET_SESSION_ERROR:
// TODO: do something with action.error
return state;
default:
return state;
}
};
module.exports.tokenReducer = function (state, action) { module.exports.tokenReducer = function (state, action) {
// Reducer for updating the api token // Reducer for updating the api token
if (typeof state === 'undefined') { if (typeof state === 'undefined') {
@ -42,41 +23,6 @@ module.exports.tokenReducer = function (state, action) {
} }
}; };
module.exports.setSessionError = function (error) {
return {
type: Types.SET_SESSION_ERROR,
error: error
};
};
module.exports.setSession = function (session) {
return {
type: Types.SET_SESSION,
session: session
};
};
module.exports.refreshSession = function () {
return function (dispatch) {
api({
host: '',
uri: '/session/'
}, function (err, body) {
if (err) return dispatch(module.exports.setSessionError(err));
if (typeof body !== 'undefined') {
if (body.banned) {
return window.location = body.url;
} else {
dispatch(module.exports.getToken());
dispatch(module.exports.setSession(body));
return;
}
}
});
};
};
module.exports.getToken = function () { module.exports.getToken = function () {
return function (dispatch) { return function (dispatch) {
jar.get('scratchsessionsid', function (err, value) { jar.get('scratchsessionsid', function (err, value) {

View file

@ -13,7 +13,7 @@ var ConferenceDetails = React.createClass({
type: 'ConferenceDetails', type: 'ConferenceDetails',
propTypes: { propTypes: {
detailsId: React.PropTypes.number, detailsId: React.PropTypes.number,
details: React.PropTypes.object conferenceDetails: React.PropTypes.object
}, },
componentDidMount: function () { componentDidMount: function () {
var pathname = window.location.pathname.toLowerCase(); var pathname = window.location.pathname.toLowerCase();
@ -26,13 +26,13 @@ var ConferenceDetails = React.createClass({
}, },
render: function () { render: function () {
var backUri = '/conference/schedule'; var backUri = '/conference/schedule';
if (!this.props.details.error && !this.props.details.fetching) { if (!this.props.conferenceDetails.error && !this.props.conferenceDetails.fetching) {
backUri = backUri + '#' + this.props.details.Day; backUri = backUri + '#' + this.props.conferenceDetails.Day;
} }
var classes = classNames({ var classes = classNames({
'inner': true, 'inner': true,
'details': true, 'details': true,
'fetching': this.props.details.fetching 'fetching': this.props.conferenceDetails.fetching
}); });
return ( return (
<div className={classes}> <div className={classes}>
@ -41,33 +41,33 @@ var ConferenceDetails = React.createClass({
&larr; Back to Full Schedule &larr; Back to Full Schedule
</a> </a>
</div> </div>
{this.props.details.error ? [ {this.props.conferenceDetails.error ? [
<h2>Agenda Item Not Found</h2> <h2>Agenda Item Not Found</h2>
] : [ ] : [
<h2>{this.props.details.Title}</h2>, <h2>{this.props.conferenceDetails.Title}</h2>,
<ul className="logistics"> <ul className="logistics">
<li> <li>
<img src="/svgs/conference/schedule/presenter-icon.svg" alt="presenter icon" /> <img src="/svgs/conference/schedule/presenter-icon.svg" alt="presenter icon" />
{this.props.details.Presenter} {this.props.conferenceDetails.Presenter}
</li> </li>
<li> <li>
<img src="/svgs/conference/schedule/time-icon.svg" alt="time icon" /> <img src="/svgs/conference/schedule/time-icon.svg" alt="time icon" />
{this.props.details.Start} &ndash; {this.props.details.End} {this.props.conferenceDetails.Start} &ndash; {this.props.conferenceDetails.End}
</li> </li>
<li> <li>
<img src="/svgs/conference/schedule/event-icon.svg" alt="event icon" /> <img src="/svgs/conference/schedule/event-icon.svg" alt="event icon" />
{this.props.details.Type} {this.props.conferenceDetails.Type}
</li> </li>
<li> <li>
<img src="/svgs/conference/schedule/location-icon.svg" alt="location icon" /> <img src="/svgs/conference/schedule/location-icon.svg" alt="location icon" />
{this.props.details.Location} {this.props.conferenceDetails.Location}
</li> </li>
</ul>, </ul>,
<div className="description"> <div className="description">
{this.props.details.Description} {this.props.conferenceDetails.Description}
</div>, </div>,
<div className="back"> <div className="back">
{this.props.details.fetching ? [] : [ {this.props.conferenceDetails.fetching ? [] : [
<a href={backUri}> <a href={backUri}>
&larr; Back to Full Schedule &larr; Back to Full Schedule
</a> </a>
@ -81,9 +81,7 @@ var ConferenceDetails = React.createClass({
var mapStateToProps = function (state) { var mapStateToProps = function (state) {
return { return {
details: state.details, conferenceDetails: state.conferenceDetails
fetching: state.fetching,
error: state.error
}; };
}; };

View file

@ -15,8 +15,7 @@ require('./schedule.scss');
var ConferenceSchedule = React.createClass({ var ConferenceSchedule = React.createClass({
type: 'ConferenceSchedule', type: 'ConferenceSchedule',
propTypes: { propTypes: {
day: React.PropTypes.string, conferenceSchedule: React.PropTypes.object
schedule: React.PropTypes.array
}, },
componentDidMount: function () { componentDidMount: function () {
var day = window.location.hash.substr(1) || 'thursday'; var day = window.location.hash.substr(1) || 'thursday';
@ -71,14 +70,14 @@ var ConferenceSchedule = React.createClass({
render: function () { render: function () {
var tabClasses = { var tabClasses = {
'thursday': classNames({ 'thursday': classNames({
'selected': (this.props.day === 'thursday') 'selected': (this.props.conferenceSchedule.day === 'thursday')
}), }),
'friday': classNames({ 'friday': classNames({
'selected': (this.props.day === 'friday') 'selected': (this.props.conferenceSchedule.day === 'friday')
}), }),
'saturday': classNames({ 'saturday': classNames({
'last': true, 'last': true,
'selected': (this.props.day === 'saturday') 'selected': (this.props.conferenceSchedule.day === 'saturday')
}) })
}; };
return ( return (
@ -106,7 +105,7 @@ var ConferenceSchedule = React.createClass({
</li> </li>
</SubNavigation> </SubNavigation>
<div className="inner"> <div className="inner">
{this.props.schedule.map(function (chunk) { {this.props.conferenceSchedule.chunks.map(function (chunk) {
return ([ return ([
<h2 key={chunk.info.name} className="breaking-title"> <h2 key={chunk.info.name} className="breaking-title">
<span>{chunk.info.name} {chunk.info.time}</span> <span>{chunk.info.name} {chunk.info.time}</span>
@ -122,10 +121,7 @@ var ConferenceSchedule = React.createClass({
var mapStateToProps = function (state) { var mapStateToProps = function (state) {
return { return {
day: state.day, conferenceSchedule: state.conferenceSchedule
schedule: state.schedule,
fetching: state.fetching,
error: state.error
}; };
}; };

View file

@ -4,7 +4,7 @@ var omit = require('lodash.omit');
var React = require('react'); var React = require('react');
var render = require('../../lib/render.jsx'); var render = require('../../lib/render.jsx');
var authActions = require('../../redux/actions.js'); var sessionActions = require('../../redux/session.js');
var Api = require('../../mixins/api.jsx'); var Api = require('../../mixins/api.jsx');
@ -167,7 +167,7 @@ var Splash = injectIntl(React.createClass({
useCsrf: true, useCsrf: true,
json: {cue: cue, value: false} json: {cue: cue, value: false}
}, function (err) { }, function (err) {
if (!err) this.props.dispatch(authActions.refreshSession()); if (!err) this.props.dispatch(sessionActions.refreshSession());
}); });
}, },
shouldShowWelcome: function () { shouldShowWelcome: function () {