├── .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 |
18 | 19 | 20 | 21 |
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 | --------------------------------------------------------------------------------