Skip to content

Commit d677265

Browse files
committed
Add simple React comment form example
This example shows how to create a comment form using only React, without any framework like Redux. On the top menu, a new link “Simple React” leads to “/simple” page. In this page, the react_on_rails gem renders the React component SimpleCommentScreen. This component is imported and exposed to the window object in the clientGlobals file. Simpler than CommentScreen (that uses Redux), the SimpleCommentScreen component handles all requests by itself: - fetchComments() gets data from ‘comments.json’ and passes it down to CommentList - handleCommentSubmit() posts the comment data from CommentForm and updates the state. Other components (CommentBox, CommentForm, CommentList) can be used in the same way as before. Extras: - MetaTagsManager now holds getCSRFToken() instead of CommentsManager - Active page is highlighted in the top menu. - Tests for “/simple” and “/comments” pages - Minor layout changes
1 parent 162eae7 commit d677265

17 files changed

+234
-56
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ vendor/ruby
2828

2929
# Generated js bundles
3030
/app/assets/javascripts/generated/*
31+
32+
# Rubymine/IntelliJ
33+
.idea

app/assets/javascripts/application.js

+5
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,8 @@
2020

2121
//= require bootstrap-sprockets
2222
//= require turbolinks
23+
24+
$(document).on('ready page:load', function () {
25+
$('nav a').parents('li,ul').removeClass('active');
26+
$('nav a[href="' + this.location.pathname + '"]').parents('li,ul').addClass('active');
27+
});

app/controllers/pages_controller.rb

+3
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,7 @@ def index
2121
# format.html
2222
# end
2323
end
24+
25+
def simple
26+
end
2427
end

app/views/comments/_form.html.erb

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
<% end %>
1313

1414
<div class="field">
15-
<%= f.label :author %><br>
15+
<%= f.label :author, 'Your Name' %><br>
1616
<%= f.text_field :author %>
1717
</div>
1818
<div class="field">
19-
<%= f.label :text %><br>
19+
<%= f.label :text, 'Say something using markdown...' %><br>
2020
<%= f.text_area :text %>
2121
</div>
2222
<div class="actions">
23-
<%= f.submit %>
23+
<%= f.submit 'Post' %>
2424
</div>
2525
<% end %>

app/views/comments/show.html.erb

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
<p id="notice"><%= notice %></p>
1+
<div class="comment">
2+
<p id="notice"><%= notice %></p>
23

3-
<p>
4-
<strong>Author:</strong>
5-
<%= @comment.author %>
6-
</p>
4+
<p>
5+
<strong>Author:</strong>
6+
<%= @comment.author %>
7+
</p>
78

8-
<p>
9-
<strong>Text:</strong>
10-
<%= @comment.text %>
11-
</p>
9+
<p>
10+
<strong>Text:</strong>
11+
<%= @comment.text %>
12+
</p>
13+
</div>
1214

1315
<%= link_to 'Edit', edit_comment_path(@comment) %> |
1416
<%= link_to 'Back', comments_path %>

app/views/layouts/application.html.erb

+3-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@
2222
<!-- Collect the nav links, forms, and other content for toggling -->
2323
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
2424
<ul class="nav navbar-nav">
25-
<li><a href="/">React Demo</a></li>
25+
<li class="active"><%= link_to "React Demo", root_path %></li>
26+
<li><%= link_to "Simple React", simple_path %></li>
2627
<li><%= link_to "Classic Rails", comments_path %></li>
27-
<li><%= link_to "by Justin Gordon, www.railsonmaui.com", "http://www.railsonmaui.com" %></li>
28+
<li><%= link_to "www.shakacode.com", "http://www.shakacode.com" %></li>
2829
<li><%= link_to "Source on Github", "https://github.com/shakacode/react-webpack-rails-tutorial" %></li>
2930
<li><%= link_to "Tutorial Article", "http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/" %></li>
3031
<li><%= link_to "Forum Discussion", "http://forum.railsonmaui.com/t/fast-rich-client-rails-development-with-webpack-and-the-es6-transpiler/82/22" %></li>

app/views/pages/index.html.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h1>Using React + Redux + Rails Backend (using the react_on_rails gem)</h1>
1+
<h2>Using React + Redux + Rails Backend (using the react_on_rails gem)</h2>
22
<ul>
33
<li>
44
If this work interests you and you're a developer or designer looking for full or part-time remote work: please

app/views/pages/simple.html.erb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<h2>Using React (no Flux framework) + Rails Backend (with react_on_rails gem)</h2>
2+
<p>This example is much simpler than the one using React + Redux and is appropriate when:</p>
3+
<ul>
4+
<li>No or minimal MVC</li>
5+
<li>No async necessary</li>
6+
</ul>
7+
<hr/>
8+
9+
<%= react_component('SimpleCommentScreen', {}, generator_function: false, prerender: false) %>

client/app/components/CommentBox.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ const CommentBox = React.createClass({
3131

3232
return (
3333
<div className="commentBox container">
34-
<h1>
34+
<h2>
3535
Comments { this.isSendingAjax() && `SENDING AJAX REQUEST! Ajax Counter is ${this.ajaxCounter()}` }
36-
</h1>
36+
</h2>
3737
<p>
3838
Text take Github Flavored Markdown. Comments older than 24 hours are deleted.
3939
<b>Name</b> is preserved, <b>Text</b> is reset, between submits.

client/app/components/CommentForm.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ const CommentForm = React.createClass({
202202
if (!this.props.error) return undefined;
203203
return (
204204
<Alert bsStyle="danger" key="commentSubmissionError">
205-
<strong>Your comment was not saved!</strong>
205+
<strong>Your comment was not saved! </strong>
206206
A server error prevented your comment from being saved. Please try again.
207207
</Alert>
208208
);

client/app/components/CommentList.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const CommentList = React.createClass({
1818
if (!this.props.error) return undefined;
1919
return (
2020
<Alert bsStyle="danger" key="commentFetchError">
21-
<strong>Comments could not be retrieved.</strong>
21+
<strong>Comments could not be retrieved. </strong>
2222
A server error prevented loading comments. Please try again.
2323
</Alert>
2424
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import React from 'react';
2+
import Immutable from 'immutable';
3+
import request from 'axios';
4+
import CommentForm from './CommentForm';
5+
import CommentList from './CommentList';
6+
import metaTagsManager from '../utils/metaTagsManager';
7+
8+
const SimpleCommentScreen = React.createClass({
9+
displayName: 'SimpleCommentScreen',
10+
11+
getInitialState() {
12+
return {
13+
$$comments: Immutable.fromJS([]),
14+
ajaxSending: false,
15+
fetchCommentsError: null,
16+
submitCommentError: null,
17+
};
18+
},
19+
20+
componentDidMount() {
21+
this.fetchComments();
22+
},
23+
24+
fetchComments() {
25+
return request.get('comments.json', { responseType: 'json' })
26+
.then(res => this.setState({ $$comments: Immutable.fromJS(res.data) }))
27+
.catch(error => this.setState({ fetchCommentsError: error }));
28+
},
29+
30+
handleCommentSubmit(comment) {
31+
this.setState({ ajaxSending: true });
32+
33+
const requestConfig = {
34+
responseType: 'json',
35+
headers: {
36+
'X-CSRF-Token': metaTagsManager.getCSRFToken(),
37+
},
38+
};
39+
40+
return request.post('comments.json', { comment }, requestConfig)
41+
.then(() => {
42+
const { $$comments } = this.state;
43+
const $$comment = Immutable.fromJS(comment);
44+
45+
this.setState({
46+
$$comments: $$comments.push($$comment),
47+
ajaxSending: false,
48+
});
49+
})
50+
.catch(error => {
51+
this.setState({
52+
submitCommentError: error,
53+
ajaxSending: false,
54+
});
55+
});
56+
},
57+
58+
render() {
59+
return (
60+
<div className="commentBox container">
61+
<h2>Comments</h2>
62+
<p>
63+
Text take Github Flavored Markdown. Comments older than 24 hours are deleted.
64+
<b> Name</b> is preserved, <b>Text</b> is reset, between submits.
65+
</p>
66+
<CommentForm
67+
ajaxSending={this.state.ajaxSending}
68+
actions={{ submitComment: this.handleCommentSubmit }}
69+
error={this.state.submitCommentError}
70+
/>
71+
<CommentList
72+
$$comments={this.state.$$comments}
73+
error={this.state.fetchCommentsError}
74+
/>
75+
</div>
76+
);
77+
},
78+
});
79+
80+
export default SimpleCommentScreen;

client/app/startup/clientGlobals.jsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
import App from './ClientApp';
2+
import SimpleCommentScreen from '../components/SimpleCommentScreen';
23

34
window.App = App;
5+
window.SimpleCommentScreen = SimpleCommentScreen;

client/app/utils/commentsManager.js

+2-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import request from 'axios';
2+
import metaTagsManager from './metaTagsManager';
23

34
const API_URL = 'comments.json';
45

@@ -29,29 +30,12 @@ const CommentsManager = {
2930
url: API_URL,
3031
responseType: 'json',
3132
headers: {
32-
'X-CSRF-Token': this.getCSRFToken(),
33+
'X-CSRF-Token': metaTagsManager.getCSRFToken(),
3334
},
3435
data: { comment },
3536
});
3637
},
3738

38-
/**
39-
* Get CSRF Token from the DOM.
40-
*
41-
* @returns {String} - CSRF Token.
42-
*/
43-
getCSRFToken() {
44-
const metas = document.getElementsByTagName('meta');
45-
for (let i = 0; i < metas.length; i++) {
46-
const meta = metas[i];
47-
if (meta.getAttribute('name') === 'csrf-token') {
48-
return meta.getAttribute('content');
49-
}
50-
}
51-
52-
return null;
53-
},
54-
5539
};
5640

5741
export default CommentsManager;

client/app/utils/metaTagsManager.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const MetaTagsManager = {
2+
3+
/**
4+
* Get CSRF Token from the DOM.
5+
*
6+
* @returns {String} - CSRF Token.
7+
*/
8+
getCSRFToken() {
9+
const metas = document.getElementsByTagName('meta');
10+
for (let i = 0; i < metas.length; i++) {
11+
const meta = metas[i];
12+
if (meta.getAttribute('name') === 'csrf-token') {
13+
return meta.getAttribute('content');
14+
}
15+
}
16+
17+
return null;
18+
},
19+
};
20+
21+
export default MetaTagsManager;

config/routes.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Rails.application.routes.draw do
22
root "pages#index"
3+
get "simple", to: "pages#simple"
34

45
resources :comments
56
end

0 commit comments

Comments
 (0)