|
| 1 | + |
| 2 | +// hardcoded dummy json data |
| 3 | +var data = [ |
| 4 | + {id: 1, author: "Pete Cunt", text: "This is one comment"}, |
| 5 | + {id: 2, author: "Jordan Walke", text: "This is *another* comment"} |
| 6 | +]; |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +// the commentList component using property data passed down from parent CommentBox |
| 11 | +var CommentList = React.createClass({ |
| 12 | + render: function() { |
| 13 | + var commentNodes = this.props.data.map(function(comment) { |
| 14 | + return ( |
| 15 | + <Comment author={comment.author} key={comment.id}> |
| 16 | + {comment.text} |
| 17 | + </Comment> |
| 18 | + ); |
| 19 | + }); |
| 20 | + return ( |
| 21 | + <div className="commentList"> |
| 22 | + {commentNodes} |
| 23 | + </div> |
| 24 | + ); |
| 25 | + } |
| 26 | +}); |
| 27 | + |
| 28 | + |
| 29 | +// the commentList component using raw data. |
| 30 | +// var CommentList = React.createClass({ |
| 31 | +// render: function() { |
| 32 | +// return ( |
| 33 | +// <div className="commentList"> |
| 34 | +// <Comment author="Pete Hunt">This is one comment</Comment> |
| 35 | +// <Comment author="Jordan Walke">This is *another* comment</Comment> |
| 36 | +// </div> |
| 37 | +// ); |
| 38 | +// } |
| 39 | +// }); |
| 40 | + |
| 41 | + |
| 42 | +// note: With the traditional DOM, input elements are rendered and the browser |
| 43 | +// manages the state (its rendered value). As a result, the state of the actual DOM |
| 44 | +// will differ from that of the component which is not what we want. |
| 45 | +// In React, components should always represent the state of the view |
| 46 | +// and not only at the point of initialization. |
| 47 | +// Hence, we use this.state to save the user's input as it is entered. |
| 48 | +// We define an initial state with two properties author and text and set them to be empty strings. |
| 49 | +// In our <input> elements, we set the value prop to reflect the state of the component |
| 50 | +// and attach onChange handlers to them. |
| 51 | +// These <input> elements with a value set are called controlled components. |
| 52 | +// Read more about controlled components here: |
| 53 | +// https://facebook.github.io/react/docs/forms.html#controlled-components |
| 54 | + |
| 55 | +var CommentForm = React.createClass({ |
| 56 | + getInitialState: function() { |
| 57 | + return {author: '', text: ''}; |
| 58 | + }, |
| 59 | + handleAuthorChange: function(e) { |
| 60 | + this.setState({author: e.target.value}); |
| 61 | + }, |
| 62 | + handleTextChange: function(e) { |
| 63 | + this.setState({text: e.target.value}); |
| 64 | + }, |
| 65 | + // clears the form fields when the form is submitted with valid input. |
| 66 | + handleSubmit: function(e) { |
| 67 | + e.preventDefault(); |
| 68 | + var author = this.state.author.trim(); |
| 69 | + var text = this.state.text.trim(); |
| 70 | + if (!text || !author) { |
| 71 | + return; |
| 72 | + } |
| 73 | + this.props.onCommentSubmit({author: author, text: text}); |
| 74 | + this.setState({author: '', text: ''}); |
| 75 | + }, |
| 76 | + render: function() { |
| 77 | + return ( |
| 78 | + <form className="commentForm" onSubmit={this.handleSubmit}> |
| 79 | + <input |
| 80 | + type="text" |
| 81 | + placeholder="Your name" |
| 82 | + value={this.state.author} |
| 83 | + onChange={this.handleAuthorChange} |
| 84 | + /> |
| 85 | + <input |
| 86 | + type="text" |
| 87 | + placeholder="Say something..." |
| 88 | + value={this.state.text} |
| 89 | + onChange={this.handleTextChange} |
| 90 | + /> |
| 91 | + <input type="submit" value="Post" /> |
| 92 | + </form> |
| 93 | + ); |
| 94 | + } |
| 95 | +}); |
| 96 | + |
| 97 | +// // the comment form component with http post |
| 98 | +// var CommentForm = React.createClass({ |
| 99 | +// render: function() { |
| 100 | +// return ( |
| 101 | +// <form className="commentForm"> |
| 102 | +// <input type="text" placeholder="Your name" /> |
| 103 | +// <input type="text" placeholder="Say something..." /> |
| 104 | +// <input type="submit" value="Post" /> |
| 105 | +// </form> |
| 106 | +// ); |
| 107 | +// } |
| 108 | +// }); |
| 109 | + |
| 110 | +// the comment form component dummy |
| 111 | +// var CommentForm = React.createClass({ |
| 112 | +// render: function() { |
| 113 | +// return ( |
| 114 | +// <div className="commentForm"> |
| 115 | +// Hello, world! I am a CommentForm. |
| 116 | +// </div> |
| 117 | +// ); |
| 118 | +// } |
| 119 | +// }); |
| 120 | + |
| 121 | +// load the list and form components. |
| 122 | +// tutorial1.js |
| 123 | +// var CommentBox = React.createClass({ |
| 124 | +// render: function() { |
| 125 | +// return ( |
| 126 | +// <div className="commentBox"> |
| 127 | +// <h1>Comments</h1> |
| 128 | +// <CommentList /> |
| 129 | +// <CommentForm /> |
| 130 | +// </div> |
| 131 | +// ); |
| 132 | +// } |
| 133 | +// }); |
| 134 | + |
| 135 | +// load the list and form components with property data that was loaded in on render. |
| 136 | +var CommentBox = React.createClass({ |
| 137 | + // sofar comments have been mutable, now we give them state, loaded into an array |
| 138 | + getInitialState: function() { |
| 139 | + return {data: []}; |
| 140 | + }, |
| 141 | + // send asynch ajax request for the fresh state of the comment data |
| 142 | + // replaces old initial state with new state from server |
| 143 | + loadCommentsFromServer: function() { |
| 144 | + $.ajax({ |
| 145 | + url: this.props.url, |
| 146 | + dataType: 'json', |
| 147 | + cache: false, |
| 148 | + success: function(data) { |
| 149 | + this.setState({data: data}); |
| 150 | + }.bind(this), |
| 151 | + error: function(xhr, status, err) { |
| 152 | + console.error(this.props.url, status, err.toString()); |
| 153 | + }.bind(this) |
| 154 | + }); |
| 155 | + }, |
| 156 | + // poll the server for new data. |
| 157 | + componentDidMount: function() { |
| 158 | + this.loadCommentsFromServer(); |
| 159 | + setInterval(this.loadCommentsFromServer, this.props.pollInterval); |
| 160 | + }, |
| 161 | + // handle comments that are submitted via the form component |
| 162 | + // When a user submits a comment, we will need to refresh the list of comments |
| 163 | + // to include the new one. It makes sense to do all of this logic in CommentBox |
| 164 | + // since CommentBox owns the state that represents the list of comments. |
| 165 | + // We need to pass data from the child component back up to its parent. |
| 166 | + // We do this by passing a callback (handleCommentSubmit) from the parent's |
| 167 | + // render method, into the child, which binds it to the child's onCommentSubmit event. |
| 168 | + // CommentBox has made the callback available to CommentForm via the onCommentSubmit prop, |
| 169 | + // the CommentForm can call the callback when the user submits the form. |
| 170 | + // Whenever the event is triggered, the callback will be invoked. |
| 171 | + |
| 172 | + // the submit callback function |
| 173 | + handleCommentSubmit: function(comment) { |
| 174 | + // instead of waiting for the request to complete before the submitted |
| 175 | + // comment appears in the list. We can optimistically add this comment to |
| 176 | + // the list before sending to the server to make the app feel faster. |
| 177 | + |
| 178 | + // the current state of the comments |
| 179 | + var comments = this.state.data; |
| 180 | + // Optimistically set an id on the new comment. It will be replaced by an |
| 181 | + // id generated by the server. In a production application you would likely |
| 182 | + // not use Date.now() for this and would have a more robust system in place. |
| 183 | + comment.id = Date.now(); |
| 184 | + var newComments = comments.concat([comment]); |
| 185 | + this.setState({data: newComments}); |
| 186 | + |
| 187 | + // submit to the server and refresh the list |
| 188 | + $.ajax({ |
| 189 | + url: this.props.url, |
| 190 | + dataType: 'json', |
| 191 | + type: 'POST', |
| 192 | + data: comment, |
| 193 | + success: function(data) { |
| 194 | + this.setState({data: data}); |
| 195 | + }.bind(this), |
| 196 | + error: function(xhr, status, err) { |
| 197 | + this.setState({data: comments}); |
| 198 | + console.error(this.props.url, status, err.toString()); |
| 199 | + }.bind(this) |
| 200 | + }); |
| 201 | + }, |
| 202 | + render: function() { |
| 203 | + return ( |
| 204 | + <div className="commentBox"> |
| 205 | + <h1>Comments</h1> |
| 206 | + <CommentList data={this.state.data} /> |
| 207 | + {/*callback is passed to form component on the onCommentSubmit event */} |
| 208 | + <CommentForm onCommentSubmit={this.handleCommentSubmit} /> |
| 209 | + {/*<CommentList data={this.props.data} />*/} |
| 210 | + {/*<CommentForm />*/} |
| 211 | + </div> |
| 212 | + ); |
| 213 | + } |
| 214 | +}); |
| 215 | + |
| 216 | +// define the comment component (without remarkable markups) |
| 217 | +// var Comment = React.createClass({ |
| 218 | +// render: function() { |
| 219 | +// return ( |
| 220 | +// <div className="comment"> |
| 221 | +// <h2 className="commentAuthor"> |
| 222 | +// {this.props.author} |
| 223 | +// </h2> |
| 224 | +// {this.props.children} |
| 225 | +// </div> |
| 226 | +// ); |
| 227 | +// } |
| 228 | +// }); |
| 229 | + |
| 230 | +// define the comment (with remarkable markups), but tags will show due to React. |
| 231 | +// var Comment = React.createClass({ |
| 232 | +// render: function() { |
| 233 | +// var md = new Remarkable(); |
| 234 | +// return ( |
| 235 | +// <div className="comment"> |
| 236 | +// <h2 className="commentAuthor"> |
| 237 | +// {this.props.author} |
| 238 | +// </h2> |
| 239 | +// {md.render(this.props.children.toString())} |
| 240 | +// </div> |
| 241 | +// ); |
| 242 | +// } |
| 243 | +// }); |
| 244 | + |
| 245 | +// for the Remarkeable markup to work, |
| 246 | +// we need to tell it to ignore Reacts default XSS guarding. |
| 247 | +// were relying on remarkable to be secure. |
| 248 | +// it is, remarkable automatically strips HTML markup and insecure links from the output. |
| 249 | +// tutorial7.js |
| 250 | +var Comment = React.createClass({ |
| 251 | + rawMarkup: function() { |
| 252 | + var md = new Remarkable(); |
| 253 | + var rawMarkup = md.render(this.props.children.toString()); |
| 254 | + return { __html: rawMarkup }; |
| 255 | + }, |
| 256 | + |
| 257 | + render: function() { |
| 258 | + return ( |
| 259 | + <div className="comment"> |
| 260 | + <h2 className="commentAuthor"> |
| 261 | + {this.props.author} |
| 262 | + </h2> |
| 263 | + <span dangerouslySetInnerHTML={this.rawMarkup()} /> |
| 264 | + </div> |
| 265 | + ); |
| 266 | + } |
| 267 | +}); |
| 268 | + |
| 269 | + |
| 270 | + |
| 271 | +// instead of using jsx tags we could also use raw js, but its more work |
| 272 | +// var CommentBox = React.createClass({displayName: 'CommentBox', |
| 273 | +// render: function() { |
| 274 | +// return ( |
| 275 | +// React.createElement('div', {className: "commentBox"}, |
| 276 | +// "Hello, world! I am a CommentBox." |
| 277 | +// ) |
| 278 | +// ); |
| 279 | +// } |
| 280 | +// }); |
| 281 | + |
| 282 | +// The render method always needs to be loaded last. it renders the main DOM node. |
| 283 | +// here we create a CommentBox and render it as Reacts root node. |
| 284 | +ReactDOM.render( |
| 285 | + // passing in hardcoded json data as a property |
| 286 | + // <CommentBox data={data} />, |
| 287 | + // passing in server endpoint and polling interval as properties |
| 288 | + <CommentBox url="/api/comments" pollInterval={2000} />, |
| 289 | + // <CommentBox url="/api/comments" />, |
| 290 | + // React.createElement(CommentBox, null), |
| 291 | + document.getElementById('content') |
| 292 | +); |
0 commit comments