├── .gitignore
├── LICENSE
├── README.md
├── comments.json
├── package.json
├── public
├── css
│ └── base.css
├── index.html
└── src
│ ├── actions
│ └── comment.js
│ ├── components
│ ├── comment-box.js
│ ├── comment-form.js
│ ├── comment-list.js
│ └── comment.js
│ ├── containers
│ └── app.js
│ ├── index.js
│ ├── lib
│ └── comment-api-driver.js
│ ├── reducers
│ └── comment.js
│ └── store
│ └── configureStore.js
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | public/dist/
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The examples provided by Facebook are for non-commercial testing and evaluation
2 | purposes only. Facebook reserves all rights not expressly granted.
3 |
4 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
7 | FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
8 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
9 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-tutorial-with-redux
2 |
3 | React comment box example rewritten with Redux and ES6 example.
4 |
5 | ## Development
6 |
7 | To run this example:
8 | 1. `npm i`
9 | 2. `npm run watch`
10 | 3. `npm start`
11 | 4. open http://localhost:3000/ in the browser
12 |
13 | ## Lisence
14 |
15 | The examples provided by Facebook are for non-commercial testing and evaluation
16 | purposes only. Facebook reserves all rights not expressly granted.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 | FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/comments.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "author": "Pete Hunt",
4 | "text": "Hey there!"
5 | },
6 | {
7 | "author": "Paul O’Shannessy",
8 | "text": "React is *great*!"
9 | },
10 | {
11 | "author": "asd",
12 | "text": "asd"
13 | },
14 | {
15 | "author": "asd",
16 | "text": "asd"
17 | },
18 | {
19 | "author": "asd",
20 | "text": "asd"
21 | },
22 | {
23 | "author": "asd",
24 | "text": "asdasd"
25 | },
26 | {
27 | "author": "asdasdas",
28 | "text": "asdasdasd"
29 | }
30 | ]
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-tutorial-with-redux",
3 | "version": "0.0.0",
4 | "description": "Code from the React tutorial with redux.",
5 | "main": "server.js",
6 | "dependencies": {
7 | "body-parser": "^1.4.3",
8 | "express": "^4.4.5",
9 | "react": "~0.13.3",
10 | "redux": "~3.0.2",
11 | "react-redux": "~3.0.1",
12 | "redux-thunk": "~1.0.0",
13 | "jquery": "~2.1.4",
14 | "redux-logger": "~2.0.1"
15 | },
16 | "devDependencies": {
17 | "browserify": "~11.2.0",
18 | "babelify": "~6.3.0",
19 | "watchify": "~3.4.0"
20 | },
21 | "scripts": {
22 | "test": "echo \"Error: no test specified\" && exit 1",
23 | "watch": "watchify --extension=js -o public/dist/bundle.js public/src/index.js",
24 | "start": "node server.js"
25 | },
26 | "browserify": {
27 | "transform": [
28 | "babelify"
29 | ]
30 | },
31 | "repository": {
32 | "type": "git",
33 | "url": "https://github.com/reactjs/react-tutorial.git"
34 | },
35 | "engines": {
36 | "node": "0.12.x"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/css/base.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: #fff;
3 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
4 | font-size: 15px;
5 | line-height: 1.7;
6 | margin: 0;
7 | padding: 30px;
8 | }
9 |
10 | a {
11 | color: #4183c4;
12 | text-decoration: none;
13 | }
14 |
15 | a:hover {
16 | text-decoration: underline;
17 | }
18 |
19 | code {
20 | background-color: #f8f8f8;
21 | border: 1px solid #ddd;
22 | border-radius: 3px;
23 | font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
24 | font-size: 12px;
25 | margin: 0 2px;
26 | padding: 0px 5px;
27 | }
28 |
29 | h1, h2, h3, h4 {
30 | font-weight: bold;
31 | margin: 0 0 15px;
32 | padding: 0;
33 | }
34 |
35 | h1 {
36 | border-bottom: 1px solid #ddd;
37 | font-size: 2.5em;
38 | font-weight: bold;
39 | margin: 0 0 15px;
40 | padding: 0;
41 | }
42 |
43 | h2 {
44 | border-bottom: 1px solid #eee;
45 | font-size: 2em;
46 | }
47 |
48 | h3 {
49 | font-size: 1.5em;
50 | }
51 |
52 | h4 {
53 | font-size: 1.2em;
54 | }
55 |
56 | p, ul {
57 | margin: 15px 0;
58 | }
59 |
60 | ul {
61 | padding-left: 30px;
62 | }
63 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Tutorial with Redux
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/public/src/actions/comment.js:
--------------------------------------------------------------------------------
1 | import {fetch, save} from '../lib/comment-api-driver';
2 |
3 | export const SUBMIT_COMMENT = 'SUBMIT_COMMENT'
4 | export const RECIEVE_COMMENTS = 'RECIEVE_COMMENTS'
5 |
6 | export function submitComment(comment) {
7 | return {
8 | type: SUBMIT_COMMENT,
9 | comment
10 | };
11 | }
12 |
13 | export function recieveComments(comments) {
14 | return {
15 | type: RECIEVE_COMMENTS,
16 | comments
17 | };
18 | }
19 |
20 | export function fetchComments() {
21 | return dispatch => {
22 | fetch("/api/comments")
23 | .then((comments) => {
24 | dispatch(recieveComments(comments.data));
25 | }).catch((error) => {
26 | console.error(error);
27 | });
28 | };
29 | }
30 |
31 | export function saveComment(comment) {
32 | return dispatch => {
33 | dispatch(submitComment(comment));
34 | save("/api/comments", comment)
35 | .then((comment) => {
36 | console.log("save comment");
37 | }).catch((error) => {
38 | console.error(error);
39 | });
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/public/src/components/comment-box.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import CommentList from './comment-list';
3 | import CommentForm from './comment-form';
4 |
5 | export default class CommentBox extends Component {
6 | componentDidMount() {
7 | this.props.fetchComments();
8 | setInterval(this.props.fetchComments, 2000);
9 | }
10 |
11 | render() {
12 | const {comments, saveComment} = this.props;
13 | return (
14 |
15 |
Comments
16 |
17 |
18 |
19 | );
20 | }
21 | }
22 |
23 | CommentBox.propTypes = {
24 | saveComment : PropTypes.func.isRequired,
25 | comments : PropTypes.array.isRequired
26 | };
27 |
--------------------------------------------------------------------------------
/public/src/components/comment-form.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | export default class CommentForm extends Component {
4 | handleSubmit(e) {
5 | e.preventDefault();
6 | const author = React.findDOMNode(this.refs.author).value.trim();
7 | const text = React.findDOMNode(this.refs.text).value.trim();
8 | if (!text || !author) {
9 | return;
10 | }
11 | this.props.onCommentSubmit({author: author, text: text});
12 | React.findDOMNode(this.refs.author).value = '';
13 | React.findDOMNode(this.refs.text).value = '';
14 | }
15 | render() {
16 | return (
17 |
22 | );
23 | }
24 | }
25 |
26 | CommentForm.propTypes = {
27 | onCommentSubmit : PropTypes.func.isRequired
28 | };
29 |
--------------------------------------------------------------------------------
/public/src/components/comment-list.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import Comment from './comment'
3 |
4 | export default class CommentList extends Component {
5 | render() {
6 | const commentNodes = this.props.comments.map((comment, index) => {
7 | return (
8 |
9 | {comment.text}
10 |
11 | );
12 | });
13 | return (
14 |
15 | {commentNodes}
16 |
17 | );
18 | }
19 | }
20 |
21 | CommentList.propTypes = {
22 | comments : PropTypes.array.isRequired
23 | };
24 |
--------------------------------------------------------------------------------
/public/src/components/comment.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | export default class Comment extends Component {
4 | rawMarkup() {
5 | let rawMarkup = marked(this.props.children.toString(), {sanitize: true});
6 | return { __html: rawMarkup };
7 | }
8 | render() {
9 | return (
10 |
11 |
12 | {this.props.author}
13 |
14 |
15 |
16 | );
17 | }
18 | }
19 |
20 | Comment.propTypes = {
21 | author : PropTypes.string.isRequired,
22 | children : PropTypes.string.isRequired
23 | };
24 |
--------------------------------------------------------------------------------
/public/src/containers/app.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { bindActionCreators } from 'redux';
3 | import CommentBox from '../components/comment-box';
4 | import * as commentActions from '../actions/comment';
5 |
6 | function mapStateToProps(state) {
7 | return {
8 | comments : state.comments
9 | };
10 | }
11 |
12 | function mapDispatchToProps(dispatch) {
13 | return bindActionCreators(commentActions, dispatch);
14 | }
15 |
16 | export default connect(
17 | mapStateToProps,
18 | mapDispatchToProps
19 | )(CommentBox);
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/public/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import configureStore from './store/configureStore';
4 | import App from './containers/app';
5 |
6 | const store = configureStore();
7 |
8 | React.render(
9 |
10 | {() => }
11 | ,
12 | document.getElementById('content')
13 | );
14 |
--------------------------------------------------------------------------------
/public/src/lib/comment-api-driver.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | export function fetch(url) {
4 | return new Promise((resolve, reject) => {
5 | $.ajax({
6 | url,
7 | dataType: 'json',
8 | cache: false,
9 | success(data) {
10 | resolve({data});
11 | },
12 | error(xhr, status, err) {
13 | reject(url, status, err.toString());
14 | }
15 | });
16 | });
17 | }
18 |
19 | export function save(url, comment) {
20 | return new Promise((resolve, reject) => {
21 | $.ajax({
22 | url,
23 | dataType: 'json',
24 | type: 'POST',
25 | data: comment,
26 | success(data) {
27 | resolve({data});
28 | },
29 | error(xhr, status, err) {
30 | reject(url, status, err.toString());
31 | }
32 | });
33 | });
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/public/src/reducers/comment.js:
--------------------------------------------------------------------------------
1 | import * as commentActions from '../actions/comment';
2 |
3 | export default function comment(state={comments : []}, action) {
4 | switch(action.type){
5 | case commentActions.SUBMIT_COMMENT:
6 | let comments = state.comments.concat([action.comment]);
7 | return {comments : comments};
8 | case commentActions.RECIEVE_COMMENTS:
9 | return {comments : action.comments};
10 | default:
11 | return state;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/public/src/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import {createStore, applyMiddleware} from 'redux';
2 | import comment from '../reducers/comment'
3 | import thunk from 'redux-thunk';
4 | import createLogger from 'redux-logger';
5 |
6 | export default function configureStore() {
7 | const logger = createLogger();
8 | const createStoreWithMiddleware = applyMiddleware(
9 | thunk, logger
10 | )(createStore);
11 | const store = createStoreWithMiddleware(comment);
12 | return store;
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file provided by Facebook is for non-commercial testing and evaluation
3 | * purposes only. Facebook reserves all rights not expressly granted.
4 | *
5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
9 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
10 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 | */
12 |
13 | var fs = require('fs');
14 | var path = require('path');
15 | var express = require('express');
16 | var bodyParser = require('body-parser');
17 | var app = express();
18 |
19 | app.set('port', (process.env.PORT || 3000));
20 |
21 | app.use('/', express.static(path.join(__dirname, 'public')));
22 | app.use(bodyParser.json());
23 | app.use(bodyParser.urlencoded({extended: true}));
24 |
25 | app.get('/api/comments', function(req, res) {
26 | fs.readFile('comments.json', function(err, data) {
27 | res.setHeader('Cache-Control', 'no-cache');
28 | res.json(JSON.parse(data));
29 | });
30 | });
31 |
32 | app.post('/api/comments', function(req, res) {
33 | fs.readFile('comments.json', function(err, data) {
34 | var comments = JSON.parse(data);
35 | comments.push(req.body);
36 | fs.writeFile('comments.json', JSON.stringify(comments, null, 4), function(err) {
37 | res.setHeader('Cache-Control', 'no-cache');
38 | res.json(comments);
39 | });
40 | });
41 | });
42 |
43 |
44 | app.listen(app.get('port'), function() {
45 | console.log('Server started: http://localhost:' + app.get('port') + '/');
46 | });
47 |
--------------------------------------------------------------------------------