diff --git a/.travis.yml b/.travis.yml
index e2d6c293c..73b329d92 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,12 +5,10 @@ rvm:
install:
- bundle install
- npm install
- - cd client && NODE_ENV=production $(npm bin)/webpack --config webpack.rails.config.js
- - NODE_ENV=production $(npm bin)/webpack --config webpack.server.config.js
-
+ - cd client && npm run build:client
+ - npm run build:server
env:
- export RAILS_ENV=test
-
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
diff --git a/Gemfile b/Gemfile
index 9cb85a43f..3508b7fe7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -39,7 +39,7 @@ gem "rails-html-sanitizer"
# Use Unicorn as the app server
gem "unicorn"
-gem "react_on_rails", "~> 0.1.3"
+gem "react_on_rails", "~> 0.1.6"
gem "therubyracer"
gem "autoprefixer-rails"
diff --git a/Gemfile.lock b/Gemfile.lock
index 13d0ac4b7..13249a8ad 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -206,7 +206,7 @@ GEM
raindrops (0.15.0)
rake (10.4.2)
rdoc (4.2.0)
- react_on_rails (0.1.3)
+ react_on_rails (0.1.6)
execjs (~> 2.5)
rails (~> 4.2)
ref (2.0.0)
@@ -351,7 +351,7 @@ DEPENDENCIES
rails-html-sanitizer
rails_12factor
rainbow
- react_on_rails (~> 0.1.3)
+ react_on_rails (~> 0.1.6)
rspec-rails
rubocop
ruby-lint
diff --git a/Procfile.dev b/Procfile.dev
index 03105c6e6..0d26caeb0 100644
--- a/Procfile.dev
+++ b/Procfile.dev
@@ -1,4 +1,4 @@
web: rails s -p 4000
-client: sh -c 'cd client && $(npm bin)/webpack -w --config webpack.rails.config.js'
-server: sh -c 'cd client && $(npm bin)/webpack -w --config webpack.server.config.js'
-hot: sh -c 'cd client && node server.js'
+client: sh -c 'rm app/assets/javascripts/generated/* || true && cd client && npm run build:dev:client'
+server: sh -c 'cd client && npm run build:dev:server'
+hot: sh -c 'cd client && npm start'
diff --git a/README.md b/README.md
index 28ca1b4eb..bfe933a5d 100644
--- a/README.md
+++ b/README.md
@@ -102,7 +102,7 @@ the JS bundle. We've chosen to let Rails handle CSS, SCSS, images, fonts.
```
cd client
-$(npm bin)/webpack -w --config webpack.rails.config.js
+npm run build:dev
```
`client-bundle.js` is generated and saved to `app/assets/javascripts`. This is included in the
@@ -182,7 +182,7 @@ jQuery and jQuery-ujs are not required within `app/assets/javascript/application
and have been moved under`/client` and managed by npm. The modules are exposed via entry point
by `webpack.common.config.js`.
-In `application.js`, it's critical that any libraries that depend on jQuery come after the inclusion
+In `application.js`, it's critical that any libraries that depend on jQuery come after the inclusion
of the Webpack bundle, such as the twitter bootstrap javascript.
Please refer to [Considerations for jQuery with Rails and Webpack](http://forum.railsonmaui.com/t/considerations-for-jquery-with-rails-and-webpack/344) for further info.
@@ -260,7 +260,7 @@ Run the tests with `rspec`.
### RubyMine/Webstorm Linting Configuration
* I started out trying to make RubyMine and WebStorm catch and fix linting errors. However, I find
it faster to just do this with the command line. Your mileage may vary.
- * Create a custom scope like this for RubyMine, named "Inspection Scope"
+ * Create a custom scope like this for RubyMine, named "Inspection Scope"
file[react-rails-tutorial]:*/&&!file[react-rails-tutorial]:tmp//*&&!file[react-rails-tutorial]:log//*&&!file[react-rails-tutorial]:client/node_modules//*&&!file[react-rails-tutorial]:client/assets/fonts//*&&!file[react-rails-tutorial]:app/assets/fonts//*&&!file[react-rails-tutorial]:bin//*&&!file[react-rails-tutorial]:app/assets/javascripts//*
@@ -289,7 +289,7 @@ WebStorm opened up to the `client` directory to focus on JSX and Sass files.
# Misc Tips
-## Cleanup local branches merged to master
+## Cleanup local branches merged to master
```
alias git-cleanup-merged-branches='git branch --merged master | grep -v master | xargs git branch -d'
```
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 34e53597a..ecd6646fe 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -13,7 +13,9 @@
// Need to be on top to allow Poltergeist test to work with React.
//= require es5-shim/es5-shim
-// It is important that generated/client-bundle must be before bootstrap since it is exposing jQuery and jQuery-ujs
-//= require generated/client-bundle
+// It is important that generated/vendor-bundle must be before bootstrap since it is exposing jQuery and jQuery-ujs
+//= require generated/vendor-bundle
+//= require generated/app-bundle
+
//= require bootstrap-sprockets
//= require turbolinks
diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb
index 39848c624..19dc5fef8 100644
--- a/app/controllers/pages_controller.rb
+++ b/app/controllers/pages_controller.rb
@@ -1,4 +1,5 @@
class PagesController < ApplicationController def index + @comments = Comment.all end end diff --git a/app/views/pages/index.html.erb b/app/views/pages/index.html.erb index 9c514b3c9..4bffec631 100644 --- a/app/views/pages/index.html.erb +++ b/app/views/pages/index.html.erb @@ -16,4 +16,4 @@
Text take Github Flavored Markdown. Comments older than 24 hours are deleted.
- Name is preserved, Text is reset, between submits.
+ Name is preserved, Text is reset, between submits.
+
+ actions={actions}
+ />
+ $$comments={data.get('$$comments')}
+ error={data.get('fetchCommentError')}
+ />
);
},
diff --git a/client/app/components/CommentForm.jsx b/client/app/components/CommentForm.jsx
new file mode 100644
index 000000000..c9b3c5dff
--- /dev/null
+++ b/client/app/components/CommentForm.jsx
@@ -0,0 +1,244 @@
+import React, { PropTypes } from 'react';
+import Input from 'react-bootstrap/lib/Input';
+import Row from 'react-bootstrap/lib/Row';
+import Col from 'react-bootstrap/lib/Col';
+import Nav from 'react-bootstrap/lib/Nav';
+import NavItem from 'react-bootstrap/lib/NavItem';
+import Alert from 'react-bootstrap/lib/Alert';
+import ReactCSSTransitionGroup from 'react/lib/ReactCSSTransitionGroup';
+
+const emptyComment = { author: '', text: '' };
+const textPlaceholder = 'Say something using markdown...';
+
+const CommentForm = React.createClass({
+ displayName: 'CommentForm',
+
+ propTypes: {
+ ajaxSending: PropTypes.bool.isRequired,
+ actions: PropTypes.object.isRequired,
+ error: PropTypes.any,
+ },
+
+ getInitialState() {
+ return {
+ formMode: 0,
+ comment: emptyComment,
+ };
+ },
+
+ handleSelect(selectedKey) {
+ this.setState({ formMode: selectedKey });
+ },
+
+ handleChange() {
+ let comment;
+
+ // This could also be done using ReactLink:
+ // http://facebook.github.io/react/docs/two-way-binding-helpers.html
+ if (this.state.formMode < 2) { + comment = { + author: this.refs.author.getValue(), + text: this.refs.text.getValue(), + }; + } else { + comment = { + // This is different because the input is a native HTML element + // rather than a React element. + author: this.refs.inlineAuthor.getDOMNode().value, + text: this.refs.inlineText.getDOMNode().value, + }; + } + + this.setState({ comment }); + }, + + handleSubmit(e) { + e.preventDefault(); + const { actions } = this.props; + actions + .submitComment(this.state.comment) + .then(this.resetAndFocus); + }, + + resetAndFocus() { + // Don't reset a form that didn't submit, this results in data loss + if (this.props.error) return; + + const comment = { author: this.state.comment.author, text: '' }; + this.setState({ comment }); + + let ref; + if (this.state.formMode < 2) { + ref = this.refs.text.getInputDOMNode(); + } else { + ref = React.findDOMNode(this.refs.inlineText); + } + + ref.focus(); + }, + + formHorizontal() { + return ( +
+
+
+
+ );
+ },
+
+ formStacked() {
+ return (
+
+
+
+
+ );
+ },
+
+ formInline() {
+ return (
+
+
+
+
+ );
+ },
+
+ errorWarning() {
+ // If there is no error, there is nothing to add to the DOM
+ if (!this.props.error) return undefined;
+ return (
+
+ Your comment was not saved!
+ A server error prevented your comment from being saved. Please try again.
+
+ );
+ },
+
+ render() {
+ let inputForm;
+ switch (this.state.formMode) {
+ case 0:
+ inputForm = this.formHorizontal();
+ break;
+ case 1:
+ inputForm = this.formStacked();
+ break;
+ case 2:
+ inputForm = this.formInline();
+ break;
+ default:
+ throw new Error(`Unknown form mode: ${this.state.formMode}.`);
+ }
+ return (
+
+
+
+ {this.errorWarning()}
+
+
+
+ {inputForm}
+
+ );
+ },
+});
+
+export default CommentForm;
diff --git a/client/assets/javascripts/components/CommentList.jsx b/client/app/components/CommentList.jsx
similarity index 55%
rename from client/assets/javascripts/components/CommentList.jsx
rename to client/app/components/CommentList.jsx
index 48a5a92ac..0f95a4e45 100644
--- a/client/assets/javascripts/components/CommentList.jsx
+++ b/client/app/components/CommentList.jsx
@@ -1,42 +1,50 @@
-import React from 'react';
-import Comment from './Comment';
+import React, { PropTypes } from 'react';
+import Immutable from 'immutable';
import Alert from 'react-bootstrap/lib/Alert';
import ReactCSSTransitionGroup from 'react/lib/ReactCSSTransitionGroup';
+import Comment from './Comment';
+
const CommentList = React.createClass({
displayName: 'CommentList',
propTypes: {
- comments: React.PropTypes.object,
- error: React.PropTypes.any.isRequired,
+ $$comments: PropTypes.instanceOf(Immutable.List).isRequired,
+ error: PropTypes.any,
},
errorWarning() {
// If there is no error, there is nothing to add to the DOM
if (!this.props.error) return undefined;
return (
-
- Comments could not be retrieved. A server error prevented loading comments. Please try again.
+
+ Comments could not be retrieved.
+ A server error prevented loading comments. Please try again.
);
},
render() {
- const commentNodes = this.props.comments.reverse().map((comment, index) => {
+ const { $$comments } = this.props;
+ const commentNodes = $$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 (
-
+
);
});
return (
-
+
{this.errorWarning()}
-
+
{commentNodes}
diff --git a/client/app/components/CommentScreen.jsx b/client/app/components/CommentScreen.jsx
new file mode 100644
index 000000000..40439789e
--- /dev/null
+++ b/client/app/components/CommentScreen.jsx
@@ -0,0 +1,49 @@
+import React, { PropTypes } from 'react';
+import CommentBox from './CommentBox';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import * as commentsActionCreators from '../actions/commentsActionCreators';
+
+function select(state) {
+ // Which part of the Redux global state does our component want to receive as props?
+ return { data: state.$$commentsStore };
+}
+
+const CommentScreen = React.createClass({
+ displayName: 'CommentScreen',
+
+ propTypes: {
+ dispatch: PropTypes.func.isRequired,
+ data: PropTypes.object.isRequired,
+ },
+
+ render() {
+ const { dispatch, data } = this.props;
+ const actions = bindActionCreators(commentsActionCreators, dispatch);
+ return (
+
+ );
+ },
+});
+
+// Don't forget to actually use connect!
+export default connect(select)(CommentScreen);
diff --git a/client/assets/javascripts/constants/ActionTypes.js b/client/app/constants/commentsConstants.js
similarity index 100%
rename from client/assets/javascripts/constants/ActionTypes.js
rename to client/app/constants/commentsConstants.js
diff --git a/client/assets/javascripts/middleware/loggerMiddleware.js b/client/app/middlewares/loggerMiddleware.js
similarity index 100%
rename from client/assets/javascripts/middleware/loggerMiddleware.js
rename to client/app/middlewares/loggerMiddleware.js
diff --git a/client/app/reducers/commentsReducer.js b/client/app/reducers/commentsReducer.js
new file mode 100644
index 000000000..5ed231b1f
--- /dev/null
+++ b/client/app/reducers/commentsReducer.js
@@ -0,0 +1,65 @@
+/* eslint new-cap: 0 */
+
+import Immutable from 'immutable';
+
+import * as actionTypes from '../constants/commentsConstants';
+
+export const $$initialState = Immutable.fromJS({
+ $$comments: [],
+ ajaxCounter: 0,
+ fetchCommentError: null,
+ submitCommentError: null,
+});
+
+export default function commentsReducer($$state = $$initialState, action) {
+ const { type, comment, comments, error } = action;
+
+ switch (type) {
+
+ case actionTypes.FETCH_COMMENTS_SUCCESS: {
+ return $$state.merge({
+ $$comments: comments,
+ fetchCommentError: null,
+ });
+ }
+
+ case actionTypes.FETCH_COMMENTS_FAILURE: {
+ return $$state.merge({
+ fetchCommentError: error,
+ });
+ }
+
+ case actionTypes.SUBMIT_COMMENT_SUCCESS: {
+ return $$state.withMutations(state => (
+ state
+ .updateIn(
+ ['$$comments'],
+ $$comments => $$comments.push(Immutable.fromJS(comment))
+ )
+ .merge({ submitCommentError: null })
+ ));
+ }
+
+ case actionTypes.SUBMIT_COMMENT_FAILURE: {
+ return $$state.merge({
+ submitCommentError: 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;
+ }
+ }
+}
diff --git a/client/app/reducers/index.js b/client/app/reducers/index.js
new file mode 100644
index 000000000..028795b53
--- /dev/null
+++ b/client/app/reducers/index.js
@@ -0,0 +1,10 @@
+import commentsReducer from './commentsReducer';
+import { $$initialState as $$commentsState } from './commentsReducer';
+
+export default {
+ $$commentsStore: commentsReducer,
+};
+
+export const initalStates = {
+ $$commentsState,
+};
diff --git a/client/assets/javascripts/ClientApp.jsx b/client/app/startup/ClientApp.jsx
similarity index 55%
rename from client/assets/javascripts/ClientApp.jsx
rename to client/app/startup/ClientApp.jsx
index fe28bb08c..dbdc2f932 100644
--- a/client/assets/javascripts/ClientApp.jsx
+++ b/client/app/startup/ClientApp.jsx
@@ -1,12 +1,13 @@
import React from 'react';
import { Provider } from 'react-redux';
-import CommentScreen from './components/CommentScreen';
-import CommentStore from './stores/CommentStore';
+import createStore from '../stores/commentsStore';
+import CommentScreen from '../components/CommentScreen';
-const App = () => {
+const App = props => {
+ const store = createStore(props);
const reactComponent = (
-
+
{() => }
);
diff --git a/client/app/startup/ServerApp.jsx b/client/app/startup/ServerApp.jsx
new file mode 100644
index 000000000..698277229
--- /dev/null
+++ b/client/app/startup/ServerApp.jsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+
+import createStore from '../stores/commentsStore';
+import CommentScreen from '../components/CommentScreen';
+
+const App = props => {
+ const store = createStore(props);
+ const reactComponent = (
+
+ {() => }
+
+ );
+ return reactComponent;
+};
+
+export default App;
diff --git a/client/assets/javascripts/clientGlobals.jsx b/client/app/startup/clientGlobals.jsx
similarity index 100%
rename from client/assets/javascripts/clientGlobals.jsx
rename to client/app/startup/clientGlobals.jsx
diff --git a/client/assets/javascripts/serverGlobals.jsx b/client/app/startup/serverGlobals.jsx
similarity index 100%
rename from client/assets/javascripts/serverGlobals.jsx
rename to client/app/startup/serverGlobals.jsx
diff --git a/client/app/stores/commentsStore.js b/client/app/stores/commentsStore.js
new file mode 100644
index 000000000..0622dce70
--- /dev/null
+++ b/client/app/stores/commentsStore.js
@@ -0,0 +1,24 @@
+import { compose, createStore, applyMiddleware, combineReducers } from 'redux';
+import thunkMiddleware from 'redux-thunk';
+import loggerMiddleware from '../middlewares/loggerMiddleware';
+import reducers from '../reducers';
+import { initalStates } from '../reducers';
+
+export default props => {
+ const initialComments = props;
+ const { $$commentsState } = initalStates;
+ const initialState = {
+ $$commentsStore: $$commentsState.merge({
+ $$comments: initialComments,
+ }),
+ };
+
+ const reducer = combineReducers(reducers);
+ const composedStore = compose(
+ applyMiddleware(thunkMiddleware, loggerMiddleware)
+ );
+ const storeCreator = composedStore(createStore);
+ const store = storeCreator(reducer, initialState);
+
+ return store;
+};
diff --git a/client/assets/javascripts/utils/CommentsManager.js b/client/app/utils/commentsManager.js
similarity index 90%
rename from client/assets/javascripts/utils/CommentsManager.js
rename to client/app/utils/commentsManager.js
index c501968f8..6698ace2e 100644
--- a/client/assets/javascripts/utils/CommentsManager.js
+++ b/client/app/utils/commentsManager.js
@@ -7,7 +7,7 @@ const CommentsManager = {
/**
* Retrieve comments from server using AJAX call.
*
- * @returns {Promise} - jqXHR result of ajax call.
+ * @returns {Promise} - result of ajax call.
*/
fetchComments() {
return request({
@@ -21,7 +21,7 @@ const CommentsManager = {
* Submit new comment to server using AJAX call.
*
* @param {Object} comment - Comment body to post.
- * @returns {Promise} - jqXHR result of ajax call.
+ * @returns {Promise} - result of ajax call.
*/
submitComment(comment) {
return request({
diff --git a/client/assets/javascripts/ServerApp.jsx b/client/assets/javascripts/ServerApp.jsx
deleted file mode 100755
index fe28bb08c..000000000
--- a/client/assets/javascripts/ServerApp.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react';
-import { Provider } from 'react-redux';
-
-import CommentScreen from './components/CommentScreen';
-import CommentStore from './stores/CommentStore';
-
-const App = () => {
- const reactComponent = (
-
- {() => }
-
- );
- return reactComponent;
-};
-
-// Export is needed for the hot reload server
-export default App;
diff --git a/client/assets/javascripts/components/Comment.jsx b/client/assets/javascripts/components/Comment.jsx
deleted file mode 100644
index d6d5a4e97..000000000
--- a/client/assets/javascripts/components/Comment.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import marked from 'marked';
-import React from 'react';
-
-const Comment = React.createClass({
- displayName: 'Comment',
-
- propTypes: {
- author: React.PropTypes.string.isRequired,
- text: React.PropTypes.string.isRequired,
- },
-
- render() {
- const rawMarkup = marked(this.props.text, { gfm: true, sanitize: true });
- return (
-
-
- {this.props.author}
-
-
-
- );
- },
-});
-
-export default Comment;
diff --git a/client/assets/javascripts/components/CommentForm.jsx b/client/assets/javascripts/components/CommentForm.jsx
deleted file mode 100644
index 1533415b0..000000000
--- a/client/assets/javascripts/components/CommentForm.jsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import React from 'react/addons';
-import Input from 'react-bootstrap/lib/Input';
-import Row from 'react-bootstrap/lib/Row';
-import Col from 'react-bootstrap/lib/Col';
-import Nav from 'react-bootstrap/lib/Nav';
-import NavItem from 'react-bootstrap/lib/NavItem';
-import Alert from 'react-bootstrap/lib/Alert';
-import ReactCSSTransitionGroup from 'react/lib/ReactCSSTransitionGroup';
-
-const emptyComment = {author: '', text: ''};
-const textPlaceholder = 'Say something using markdown...';
-
-const CommentForm = React.createClass({
- displayName: 'CommentForm',
-
- propTypes: {
- ajaxSending: React.PropTypes.bool.isRequired,
- actions: React.PropTypes.object.isRequired,
- error: React.PropTypes.any.isRequired,
- },
-
- getInitialState() {
- return {
- formMode: 0,
- comment: emptyComment,
- };
- },
-
- handleSelect(selectedKey) {
- this.setState({formMode: selectedKey});
- },
-
- handleChange() {
- let comment;
-
- // This could also be done using ReactLink:
- // http://facebook.github.io/react/docs/two-way-binding-helpers.html
- if (this.state.formMode < 2) { - comment = { - author: this.refs.author.getValue(), - text: this.refs.text.getValue(), - }; - } else { - comment = { - // This is different because the input is a native HTML element - // rather than a React element. - author: this.refs.inlineAuthor.getDOMNode().value, - text: this.refs.inlineText.getDOMNode().value, - }; - } - - this.setState({comment}); - }, - - handleSubmit(e) { - e.preventDefault(); - this.props.actions.submitComment(this.state.comment).then( - () => this.resetAndFocus()
- );
- },
-
- resetAndFocus() {
- // Don't reset a form that didn't submit, this results in data loss
- if (this.props.error) return;
-
- const comment = {author: this.state.comment.author, text: ''};
- this.setState({comment});
-
- let ref;
- if (this.state.formMode < 2) { - ref = this.refs.text.getInputDOMNode(); - } else { - ref = React.findDOMNode(this.refs.inlineText); - } - - ref.focus(); - }, - - formHorizontal() { - return ( -
-
-
-
- );
- },
-
- formStacked() {
- return (
-
-
-
-
- );
- },
-
- formInline() {
- return (
-
-
-
-
- );
- },
-
- errorWarning() {
- // If there is no error, there is nothing to add to the DOM
- if (!this.props.error) return undefined;
- return (
-
- Your comment was not saved! A server error prevented your comment from being saved. Please try
- again.
-
- );
- },
-
- render() {
- let inputForm;
- switch (this.state.formMode) {
- case 0:
- inputForm = this.formHorizontal();
- break;
- case 1:
- inputForm = this.formStacked();
- break;
- case 2:
- inputForm = this.formInline();
- break;
- default:
- throw new Error(`Unknown form mode: ${this.state.formMode}.`);
- }
- return (
-
-
-
- {this.errorWarning()}
-
-
-
- {inputForm}
-
- );
- },
-});
-
-export default CommentForm;
diff --git a/client/assets/javascripts/components/CommentScreen.jsx b/client/assets/javascripts/components/CommentScreen.jsx
deleted file mode 100644
index 843c37a53..000000000
--- a/client/assets/javascripts/components/CommentScreen.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import CommentBox from './CommentBox';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { fetchComments, submitComment } from '../actions/CommentActionCreators';
-
-const CommentActionsCreators = { fetchComments, submitComment };
-
-function select(state) {
- // Which part of the Redux global state does our component want to receive as props?
- return { data: state.commentsData };
-}
-
-const CommentScreen = React.createClass({
- displayName: 'CommentScreen',
-
- propTypes: {
- dispatch: React.PropTypes.func.isRequired,
- data: React.PropTypes.object.isRequired,
- },
-
- render() {
- const { dispatch, data } = this.props;
- const actions = bindActionCreators(CommentActionsCreators, dispatch);
- return (
-