├── requirements.txt ├── .gitignore ├── .editorconfig ├── app.json ├── LICENSE ├── comments.json ├── package.json ├── public ├── index.html ├── css │ └── base.css └── scripts │ └── example.js ├── README.md ├── server.pl ├── server.py ├── server.rb ├── server.php ├── server.js └── server.go /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.{js,rb,css,html}] 13 | indent_size = 2 14 | 15 | [*.go] 16 | indent_size = 8 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Tutorial Server", 3 | "description": "Code from the React tutorial", 4 | "keywords": [ "react", "reactjs", "tutorial" ], 5 | "repository": "https://github.com/reactjs/react-tutorial", 6 | "logo": "https://facebook.github.io/react/img/logo.svg", 7 | "website": "http://facebook.github.io/react/docs/tutorial.html", 8 | "success_url": "/", 9 | "env" : { 10 | "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-nodejs.git" 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /comments.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1388534400000, 4 | "author": "Pete Hunt", 5 | "text": "Hey there!" 6 | }, 7 | { 8 | "id": 1420070400000, 9 | "author": "Paul O’Shannessy", 10 | "text": "React is *great*!" 11 | }, 12 | { 13 | "id": 1464988635157, 14 | "author": "ben", 15 | "text": "*abc*" 16 | }, 17 | { 18 | "id": 1464988636500, 19 | "author": "ben", 20 | "text": "*abc*" 21 | }, 22 | { 23 | "id": 1464988717637, 24 | "author": "evil", 25 | "text": "alert(1)" 26 | } 27 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-tutorial", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "see LICENSE file", 6 | "description": "Code from the React tutorial.", 7 | "main": "server.js", 8 | "dependencies": { 9 | "body-parser": "^1.4.3", 10 | "express": "^4.4.5" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "start": "node server.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/reactjs/react-tutorial.git" 20 | }, 21 | "keywords": [ 22 | "react", 23 | "tutorial", 24 | "comment", 25 | "example" 26 | ], 27 | "author": "petehunt", 28 | "bugs": { 29 | "url": "https://github.com/reactjs/react-tutorial/issues" 30 | }, 31 | "homepage": "https://github.com/reactjs/react-tutorial", 32 | "engines" : { 33 | "node" : "0.12.x" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Tutorial 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /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: 0 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 | } 39 | 40 | h2 { 41 | border-bottom: 1px solid #eee; 42 | font-size: 2em; 43 | } 44 | 45 | h3 { 46 | font-size: 1.5em; 47 | } 48 | 49 | h4 { 50 | font-size: 1.2em; 51 | } 52 | 53 | p, ul { 54 | margin: 15px 0; 55 | } 56 | 57 | ul { 58 | padding-left: 30px; 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 2 | 3 | # React Tutorial 4 | 5 | This is the React comment box example from [the React tutorial](http://facebook.github.io/react/docs/tutorial.html). 6 | 7 | ## To use 8 | 9 | There are several simple server implementations included. They all serve static files from `public/` and handle requests to `/api/comments` to fetch or add data. Start a server with one of the following: 10 | 11 | ### Node 12 | 13 | ```sh 14 | npm install 15 | node server.js 16 | ``` 17 | 18 | ### Python 19 | 20 | ```sh 21 | pip install -r requirements.txt 22 | python server.py 23 | ``` 24 | 25 | ### Ruby 26 | ```sh 27 | ruby server.rb 28 | ``` 29 | 30 | ### PHP 31 | ```sh 32 | php server.php 33 | ``` 34 | 35 | ### Go 36 | ```sh 37 | go run server.go 38 | ``` 39 | 40 | ### Perl 41 | 42 | ```sh 43 | cpan Mojolicious 44 | perl server.pl 45 | ``` 46 | 47 | And visit . Try opening multiple tabs! 48 | 49 | ## Changing the port 50 | 51 | You can change the port number by setting the `$PORT` environment variable before invoking any of the scripts above, e.g., 52 | 53 | ```sh 54 | PORT=3001 node server.js 55 | ``` 56 | -------------------------------------------------------------------------------- /server.pl: -------------------------------------------------------------------------------- 1 | # This file provided by Facebook is 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 | 11 | use Time::HiRes qw(gettimeofday); 12 | use Mojolicious::Lite; 13 | use Mojo::JSON qw(encode_json decode_json); 14 | 15 | app->static->paths->[0] = './public'; 16 | 17 | any '/' => sub { $_[0]->reply->static('index.html') }; 18 | 19 | any [qw(GET POST)] => '/api/comments' => sub { 20 | my $self = shift; 21 | my $comments = decode_json (do { local(@ARGV,$/) = 'comments.json';<> }); 22 | $self->res->headers->cache_control('no-cache'); 23 | $self->res->headers->access_control_allow_origin('*'); 24 | 25 | if ($self->req->method eq 'POST') 26 | { 27 | push @$comments, { 28 | id => int(gettimeofday * 1000), 29 | author => $self->param('author'), 30 | text => $self->param('text'), 31 | }; 32 | open my $FILE, '>', 'comments.json'; 33 | print $FILE encode_json($comments); 34 | } 35 | $self->render(json => $comments); 36 | }; 37 | my $port = $ENV{PORT} || 3000; 38 | app->start('daemon', '-l', "http://*:$port"); 39 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | # This file provided by Facebook is 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 | 11 | import json 12 | import os 13 | import time 14 | from flask import Flask, Response, request 15 | 16 | app = Flask(__name__, static_url_path='', static_folder='public') 17 | app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html')) 18 | 19 | 20 | @app.route('/api/comments', methods=['GET', 'POST']) 21 | def comments_handler(): 22 | with open('comments.json', 'r') as f: 23 | comments = json.loads(f.read()) 24 | 25 | if request.method == 'POST': 26 | new_comment = request.form.to_dict() 27 | new_comment['id'] = int(time.time() * 1000) 28 | comments.append(new_comment) 29 | 30 | with open('comments.json', 'w') as f: 31 | f.write(json.dumps(comments, indent=4, separators=(',', ': '))) 32 | 33 | return Response( 34 | json.dumps(comments), 35 | mimetype='application/json', 36 | headers={ 37 | 'Cache-Control': 'no-cache', 38 | 'Access-Control-Allow-Origin': '*' 39 | } 40 | ) 41 | 42 | 43 | if __name__ == '__main__': 44 | app.run(port=int(os.environ.get("PORT", 3000)), debug=True) 45 | -------------------------------------------------------------------------------- /server.rb: -------------------------------------------------------------------------------- 1 | # This file provided by Facebook is 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 | 11 | require 'webrick' 12 | require 'json' 13 | 14 | # default port to 3000 or overwrite with PORT variable by running 15 | # $ PORT=3001 ruby server.rb 16 | port = ENV['PORT'] ? ENV['PORT'].to_i : 3000 17 | 18 | puts "Server started: http://localhost:#{port}/" 19 | 20 | root = File.expand_path './public' 21 | server = WEBrick::HTTPServer.new Port: port, DocumentRoot: root 22 | 23 | server.mount_proc '/api/comments' do |req, res| 24 | comments = JSON.parse(File.read('./comments.json', encoding: 'UTF-8')) 25 | 26 | if req.request_method == 'POST' 27 | # Assume it's well formed 28 | comment = { id: (Time.now.to_f * 1000).to_i } 29 | req.query.each do |key, value| 30 | comment[key] = value.force_encoding('UTF-8') unless key == 'id' 31 | end 32 | comments << comment 33 | File.write( 34 | './comments.json', 35 | JSON.pretty_generate(comments, indent: ' '), 36 | encoding: 'UTF-8' 37 | ) 38 | end 39 | 40 | # always return json 41 | res['Content-Type'] = 'application/json' 42 | res['Cache-Control'] = 'no-cache' 43 | res['Access-Control-Allow-Origin'] = '*' 44 | res.body = JSON.generate(comments) 45 | end 46 | 47 | trap('INT') { server.shutdown } 48 | 49 | server.start 50 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | round(microtime(true) * 1000), 39 | 'author' => $_POST['author'], 40 | 'text' => $_POST['text'] 41 | ]; 42 | 43 | $comments = json_encode($commentsDecoded, JSON_PRETTY_PRINT); 44 | file_put_contents('comments.json', $comments); 45 | } 46 | header('Content-Type: application/json'); 47 | header('Cache-Control: no-cache'); 48 | header('Access-Control-Allow-Origin: *'); 49 | echo $comments; 50 | } else { 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /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 | var COMMENTS_FILE = path.join(__dirname, 'comments.json'); 20 | 21 | app.set('port', (process.env.PORT || 3000)); 22 | 23 | app.use('/', express.static(path.join(__dirname, 'public'))); 24 | app.use(bodyParser.json()); 25 | app.use(bodyParser.urlencoded({extended: true})); 26 | 27 | // Additional middleware which will set headers that we need on each request. 28 | app.use(function(req, res, next) { 29 | // Set permissive CORS header - this allows this server to be used only as 30 | // an API server in conjunction with something like webpack-dev-server. 31 | res.setHeader('Access-Control-Allow-Origin', '*'); 32 | 33 | // Disable caching so we'll always get the latest comments. 34 | res.setHeader('Cache-Control', 'no-cache'); 35 | next(); 36 | }); 37 | 38 | app.get('/api/comments', function(req, res) { 39 | fs.readFile(COMMENTS_FILE, function(err, data) { 40 | if (err) { 41 | console.error(err); 42 | process.exit(1); 43 | } 44 | res.json(JSON.parse(data)); 45 | }); 46 | }); 47 | 48 | app.post('/api/comments', function(req, res) { 49 | fs.readFile(COMMENTS_FILE, function(err, data) { 50 | if (err) { 51 | console.error(err); 52 | process.exit(1); 53 | } 54 | var comments = JSON.parse(data); 55 | // NOTE: In a real implementation, we would likely rely on a database or 56 | // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll 57 | // treat Date.now() as unique-enough for our purposes. 58 | var newComment = { 59 | id: Date.now(), 60 | author: req.body.author, 61 | text: req.body.text, 62 | }; 63 | comments.push(newComment); 64 | fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) { 65 | if (err) { 66 | console.error(err); 67 | process.exit(1); 68 | } 69 | res.json(comments); 70 | }); 71 | }); 72 | }); 73 | 74 | 75 | app.listen(app.get('port'), function() { 76 | console.log('Server started: http://localhost:' + app.get('port') + '/'); 77 | }); 78 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 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 | package main 14 | 15 | import ( 16 | "bytes" 17 | "encoding/json" 18 | "fmt" 19 | "io" 20 | "io/ioutil" 21 | "log" 22 | "net/http" 23 | "os" 24 | "sync" 25 | "time" 26 | ) 27 | 28 | type comment struct { 29 | ID int64 `json:"id"` 30 | Author string `json:"author"` 31 | Text string `json:"text"` 32 | } 33 | 34 | const dataFile = "./comments.json" 35 | 36 | var commentMutex = new(sync.Mutex) 37 | 38 | // Handle comments 39 | func handleComments(w http.ResponseWriter, r *http.Request) { 40 | // Since multiple requests could come in at once, ensure we have a lock 41 | // around all file operations 42 | commentMutex.Lock() 43 | defer commentMutex.Unlock() 44 | 45 | // Stat the file, so we can find its current permissions 46 | fi, err := os.Stat(dataFile) 47 | if err != nil { 48 | http.Error(w, fmt.Sprintf("Unable to stat the data file (%s): %s", dataFile, err), http.StatusInternalServerError) 49 | return 50 | } 51 | 52 | // Read the comments from the file. 53 | commentData, err := ioutil.ReadFile(dataFile) 54 | if err != nil { 55 | http.Error(w, fmt.Sprintf("Unable to read the data file (%s): %s", dataFile, err), http.StatusInternalServerError) 56 | return 57 | } 58 | 59 | switch r.Method { 60 | case "POST": 61 | // Decode the JSON data 62 | var comments []comment 63 | if err := json.Unmarshal(commentData, &comments); err != nil { 64 | http.Error(w, fmt.Sprintf("Unable to Unmarshal comments from data file (%s): %s", dataFile, err), http.StatusInternalServerError) 65 | return 66 | } 67 | 68 | // Add a new comment to the in memory slice of comments 69 | comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")}) 70 | 71 | // Marshal the comments to indented json. 72 | commentData, err = json.MarshalIndent(comments, "", " ") 73 | if err != nil { 74 | http.Error(w, fmt.Sprintf("Unable to marshal comments to json: %s", err), http.StatusInternalServerError) 75 | return 76 | } 77 | 78 | // Write out the comments to the file, preserving permissions 79 | err := ioutil.WriteFile(dataFile, commentData, fi.Mode()) 80 | if err != nil { 81 | http.Error(w, fmt.Sprintf("Unable to write comments to data file (%s): %s", dataFile, err), http.StatusInternalServerError) 82 | return 83 | } 84 | 85 | w.Header().Set("Content-Type", "application/json") 86 | w.Header().Set("Cache-Control", "no-cache") 87 | w.Header().Set("Access-Control-Allow-Origin", "*") 88 | io.Copy(w, bytes.NewReader(commentData)) 89 | 90 | case "GET": 91 | w.Header().Set("Content-Type", "application/json") 92 | w.Header().Set("Cache-Control", "no-cache") 93 | w.Header().Set("Access-Control-Allow-Origin", "*") 94 | // stream the contents of the file to the response 95 | io.Copy(w, bytes.NewReader(commentData)) 96 | 97 | default: 98 | // Don't know the method, so error 99 | http.Error(w, fmt.Sprintf("Unsupported method: %s", r.Method), http.StatusMethodNotAllowed) 100 | } 101 | } 102 | 103 | func main() { 104 | port := os.Getenv("PORT") 105 | if port == "" { 106 | port = "3000" 107 | } 108 | http.HandleFunc("/api/comments", handleComments) 109 | http.Handle("/", http.FileServer(http.Dir("./public"))) 110 | log.Println("Server started: http://localhost:" + port) 111 | log.Fatal(http.ListenAndServe(":"+port, nil)) 112 | } 113 | -------------------------------------------------------------------------------- /public/scripts/example.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 Comment = React.createClass({ 14 | rawMarkup: function() { 15 | var md = new Remarkable(); 16 | var rawMarkup = md.render(this.props.children.toString()); 17 | return { __html: rawMarkup }; 18 | }, 19 | 20 | render: function() { 21 | return ( 22 |
23 |

24 | {this.props.author} 25 |

26 | 27 |
28 | ); 29 | } 30 | }); 31 | 32 | var CommentBox = React.createClass({ 33 | loadCommentsFromServer: function() { 34 | $.ajax({ 35 | url: this.props.url, 36 | dataType: 'json', 37 | cache: false, 38 | success: function(data) { 39 | this.setState({data: data}); 40 | }.bind(this), 41 | error: function(xhr, status, err) { 42 | console.error(this.props.url, status, err.toString()); 43 | }.bind(this) 44 | }); 45 | }, 46 | handleCommentSubmit: function(comment) { 47 | var comments = this.state.data; 48 | // Optimistically set an id on the new comment. It will be replaced by an 49 | // id generated by the server. In a production application you would likely 50 | // not use Date.now() for this and would have a more robust system in place. 51 | comment.id = Date.now(); 52 | var newComments = comments.concat([comment]); 53 | this.setState({data: newComments}); 54 | $.ajax({ 55 | url: this.props.url, 56 | dataType: 'json', 57 | type: 'POST', 58 | data: comment, 59 | success: function(data) { 60 | this.setState({data: data}); 61 | }.bind(this), 62 | error: function(xhr, status, err) { 63 | this.setState({data: comments}); 64 | console.error(this.props.url, status, err.toString()); 65 | }.bind(this) 66 | }); 67 | }, 68 | getInitialState: function() { 69 | return {data: []}; 70 | }, 71 | componentDidMount: function() { 72 | this.loadCommentsFromServer(); 73 | setInterval(this.loadCommentsFromServer, this.props.pollInterval); 74 | }, 75 | render: function() { 76 | return ( 77 |
78 |

Comments

79 | 80 | 81 |
82 | ); 83 | } 84 | }); 85 | 86 | var CommentList = React.createClass({ 87 | render: function() { 88 | var commentNodes = this.props.data.map(function(comment) { 89 | return ( 90 | 91 | {comment.text} 92 | 93 | ); 94 | }); 95 | return ( 96 |
97 | {commentNodes} 98 |
99 | ); 100 | } 101 | }); 102 | 103 | var CommentForm = React.createClass({ 104 | getInitialState: function() { 105 | return {author: '', text: ''}; 106 | }, 107 | handleAuthorChange: function(e) { 108 | this.setState({author: e.target.value}); 109 | }, 110 | handleTextChange: function(e) { 111 | this.setState({text: e.target.value}); 112 | }, 113 | handleSubmit: function(e) { 114 | e.preventDefault(); 115 | var author = this.state.author.trim(); 116 | var text = this.state.text.trim(); 117 | if (!text || !author) { 118 | return; 119 | } 120 | this.props.onCommentSubmit({author: author, text: text}); 121 | this.setState({author: '', text: ''}); 122 | }, 123 | render: function() { 124 | return ( 125 |
126 | 132 | 138 | 139 |
140 | ); 141 | } 142 | }); 143 | 144 | ReactDOM.render( 145 | , 146 | document.getElementById('content') 147 | ); 148 | --------------------------------------------------------------------------------