Skip to content

Commit

Permalink
Working version with Redux with a few isssues:
Browse files Browse the repository at this point in the history
Squash merge of
#60 from
@alexfedoseev (thanks!)

1. What should we name the reducer? Seems confusing in terms of how the
reducer name gets put as part of the state.
2. Functional issue in that we don't clear out the last name/commment
after ajax is successful. Seems that we would need to have the component
state inside of the main store.
3. Ajax counter to show busy indicator of async in progress might not be
working.
4. We should show a simple validation on the server and the display of a
simple error message on the form.
  • Loading branch information
justin808 committed Aug 6, 2015
1 parent c2d808a commit 33a55a3
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 71 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ npm-debug.log

# Ignore bundle dependencies
vendor/ruby

# RVM gemset
.ruby-gemset
44 changes: 22 additions & 22 deletions client/assets/javascripts/actions/CommentActionCreators.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,66 @@
import CommentsManager from '../utils/CommentsManager';
import * as types from '../constants/ActionTypes';
import * as actionTypes from '../constants/ActionTypes';

export function incrementAjaxCounter() {
return {
type: types.INCREMENT_AJAX_COUNTER
}
type: actionTypes.INCREMENT_AJAX_COUNTER,
};
}

export function decrementAjaxCounter() {
return {
type: types.DECREMENT_AJAX_COUNTER
}
type: actionTypes.DECREMENT_AJAX_COUNTER,
};
}

export function fetchCommentsSuccess(comments) {
return {
type: types.FETCH_COMMENTS_SUCCESS,
comments
type: actionTypes.FETCH_COMMENTS_SUCCESS,
comments,
};
}

export function fetchCommentsFailure(error) {
return {
type: types.FETCH_COMMENTS_FAILURE,
error
type: actionTypes.FETCH_COMMENTS_FAILURE,
error,
};
}

export function submitCommentSuccess(comment) {
return {
type: types.SUBMIT_COMMENT_SUCCESS,
comment
type: actionTypes.SUBMIT_COMMENT_SUCCESS,
comment,
};
}

export function submitCommentFailure(error) {
return {
type: types.SUBMIT_COMMENT_FAILURE,
error
}
type: actionTypes.SUBMIT_COMMENT_FAILURE,
error,
};
}

export function fetchComments() {
return function(dispatch) {
return dispatch => {
return CommentsManager.fetchComments().then(
comments => dispatch(fetchCommentsSuccess(comments)),
error => dispatch(fetchCommentsFailure(error))
comments => dispatch(fetchCommentsSuccess(comments)),
error => dispatch(fetchCommentsFailure(error))
);
}
};
}

function dispatchDecrementAjaxCounter() {
function dispatchDecrementAjaxCounter(dispatch) {
return dispatch(decrementAjaxCounter());
}

export function submitComment(comment) {
return function(dispatch) {
return dispatch => {
dispatch(incrementAjaxCounter());
return CommentsManager.submitComment(comment).then(
comment => dispatch(submitCommentSuccess(comment)),
error => dispatch(submitCommentFailure(error))).then(
dispatchDecrementAjaxCounter,
dispatchDecrementAjaxCounter);
dispatchDecrementAjaxCounter(dispatch),
dispatchDecrementAjaxCounter(dispatch));
}
}
4 changes: 2 additions & 2 deletions client/assets/javascripts/components/Comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const Comment = React.createClass({
render() {
const rawMarkup = marked(this.props.text);
return (
<div className='comment'>
<h2 className='comment-author'>
<div className="comment">
<h2 className="comment-author">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}}/>
Expand Down
9 changes: 7 additions & 2 deletions client/assets/javascripts/components/CommentBox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ const CommentBox = React.createClass({
},

render() {
console.log("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
console.log("commentBox, this.props is %O", this.props);
console.log("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ");
const { actions, data } = this.props;

return (
<div className='commentBox container'>
<h1>Comments { this.props.ajaxCounter > 0 ? `SENDING AJAX REQUEST! Ajax Counter is ${this.props.ajaxCounter}` : '' }</h1>
<CommentForm ajaxSending={this.props.ajaxCounter > 0} actions={this.props.actions}/>
<CommentList comments={this.props.comments}/>
<CommentForm ajaxSending={this.props.ajaxCounter > 0} actions={actions}/>
<CommentList comments={data.get('comments')}/>
</div>
);
},
Expand Down
7 changes: 3 additions & 4 deletions client/assets/javascripts/components/CommentList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ const CommentList = React.createClass({
displayName: 'CommentList',

propTypes: {
comments: React.PropTypes.array,
comments: React.PropTypes.object,
},

render() {
const reversedData = this.props.comments.slice(0).reverse();
const commentNodes = reversedData.map((comment, index) => {
const commentNodes = this.props.comments.reverse().map((comment, index) => {
// `key` is a React-specific concept and is not mandatory for the
// purpose of this tutorial. if you're curious, see more here:
// http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
return (
<Comment author={comment.author} key={index} text={comment.text}/>
<Comment author={comment.get('author')} key={index} text={comment.get('text')}/>
);
});

Expand Down
11 changes: 6 additions & 5 deletions client/assets/javascripts/components/CommentScreen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchComments, submitComment } from '../actions/CommentActionCreators';

const CommentActionsCreators = [fetchComments, submitComment];
const CommentActionsCreators = { fetchComments, submitComment };

function select(state) {
// Which part of the Redux global state does our component want to receive as props?
return state;
return { data: state.commentsData };
}

const CommentScreen = React.createClass({
Expand All @@ -19,11 +19,12 @@ const CommentScreen = React.createClass({
},

render() {
const { dispatch, ...other } = this.props;
const actions = bindActionCreators(CommentActionCreators, dispatch);
const { dispatch, data } = this.props;
const actions = bindActionCreators(CommentActionsCreators, dispatch);
return (
<div>
<CommentBox pollInterval={5000} {...other}
<CommentBox pollInterval={5000}
data={data}
actions={actions}/>

<div className='container'>
Expand Down
39 changes: 20 additions & 19 deletions client/assets/javascripts/reducers/commentReducer.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import * as types from '../constants/ActionTypes';
import * as actionTypes from '../constants/ActionTypes';

import Immutable from 'immutable';
import { Map, List } from 'immutable';

const initialState = Immutable.Map({
comments: [],
const initialState = Map({
comments: List(),
ajaxCounter: 0,
fetchCommentError: '',
submitCommentError: '',
});

export default function commentReducer(state = initialState, action) {
export default function commentsReducer(state = initialState, action) {
switch (action.type) {
case types.FETCH_COMMENTS_SUCCESS:
return state.merge({ comments: action.comments, fetchCommentError: '' });
case types.FETCH_COMMENTS_FAILURE:
return state.merge({ fetchCommentError: action.error });
case types.SUBMIT_COMMENT_SUCCESS:
return state.withMutatations(mState => {
mState.updateIn(['comments'], comments => comments.unshift(action.comment)).
merge({ submitCommentError: '' });
case actionTypes.FETCH_COMMENTS_SUCCESS:
return state.merge({comments: action.comments, fetchCommentError: ''});
case actionTypes.FETCH_COMMENTS_FAILURE:
return state.merge({fetchCommentError: action.error});
case actionTypes.SUBMIT_COMMENT_SUCCESS:
return state.withMutations(mState => {
mState
.updateIn(['comments'], comments => comments.push(Map(action.comment)))
.merge({submitCommentError: ''});
});
case types.SUBMIT_COMMENT_FAILURE:
return state.merge({ submitCommentError: action.error });
case types.INCREMENT_AJAX_COUNTER:
return state.merge({ ajaxCounter: state.get('ajaxCounter') + 1 });
case types.DECREMENT_AJAX_COUNTER:
return state.merge({ ajaxCounter: state.get('ajaxCounter') - 1 });
case actionTypes.SUBMIT_COMMENT_FAILURE:
return state.merge({submitCommentError: action.error});
case actionTypes.INCREMENT_AJAX_COUNTER:
return state.merge({ajaxCounter: state.get('ajaxCounter') + 1});
case actionTypes.DECREMENT_AJAX_COUNTER:
return state.merge({ajaxCounter: state.get('ajaxCounter') - 1});
default:
return state;
}
Expand Down
3 changes: 3 additions & 0 deletions client/assets/javascripts/reducers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import commentsData from './commentReducer';

export default { commentsData };
12 changes: 7 additions & 5 deletions client/assets/javascripts/stores/CommentStore.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createStore, applyMiddleware } from 'redux';
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import commentReducer from '../reducers/commentReducer';
import loggerMiddleware from '../middleware/loggerMiddleware';
import reducers from '../reducers';

// Smth wrong with this one, off for now
// import loggerMiddleware from '../middleware/loggerMiddleware';

// applyMiddleware supercharges createStore with middleware:
const createStoreWithMiddleware = applyMiddleware(thunk, loggerMiddleware)(createStore);
export default createStoreWithMiddleware(commentReducer);
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
export default createStoreWithMiddleware(combineReducers(reducers));
10 changes: 4 additions & 6 deletions client/gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const gulp = require('gulp');
const eslint = require('gulp-eslint');
const eslint = require('eslint/lib/cli');

// Note: To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failOnError last.
gulp.task('lint', function gulpLint() {
return gulp.src(['assets/javascripts/**/*.jsx', 'scripts/**/*.jsx', '*.js'])
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failOnError());
gulp.task('lint', function gulpLint(done) {
eslint.execute('--ext .js,.jsx .');
return done();
});

gulp.task('default', ['lint'], function gulpDefault() {
Expand Down
16 changes: 12 additions & 4 deletions client/server.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
/*eslint-disable no-console, func-names, no-var */
/* eslint-disable no-console, func-names, no-var */
var bodyParser = require('body-parser');
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.hot.config');
var sleep = require('sleep');

var comments = [{author: 'Pete Hunt', text: 'Hey there!'},
{author: 'Justin Gordon', text: 'Aloha from @railsonmaui'},];
var comments = [
{author: 'Pete Hunt', text: 'Hey there!'},
{author: 'Justin Gordon', text: 'Aloha from @railsonmaui'},
];

var server = new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
noInfo: false,
stats: {colors: true},
stats: {
colors: true,
hash: false,
version: false,
chunks: false,
children: false,
},
});

server.app.use(bodyParser.json(null));
Expand Down
6 changes: 4 additions & 2 deletions client/webpack.common.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ module.exports = {
// jquery: 'var jQuery'
// },
resolve: {
root: [path.join(__dirname, 'scripts'),
path.join(__dirname, 'assets/javascripts'),],
root: [
path.join(__dirname, 'scripts'),
path.join(__dirname, 'assets/javascripts'),
],
extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.scss', '.css', 'config.js'],
},
module: {
Expand Down

0 comments on commit 33a55a3

Please sign in to comment.