comments.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. /*
  2. Good practices:
  3. - name CSS classNamees like the associated component
  4. - prefix non-React method names by an "_"
  5. - pass a "key" attribute to elements in a loop
  6. State
  7. - direct reads, write through setState()
  8. - declare initial state in constructo()
  9. Refs:
  10. - are called by React during render()
  11. - (foo) => { this._bar = foo; ) translates to function(foo) { this._bar = foo; }.bind(this);
  12. Events:
  13. - React events are "synthetic" events, masking the browser behaviour differences
  14. - https://facebook.github.io/react/docs/events.html
  15. JSX:
  16. - "className", not "class"
  17. - knows how to render an array of JSX elements, not just one.
  18. */
  19. class Comment extends React.Component {
  20. render() {
  21. return (
  22. <div className="comment">
  23. <p className="comment-header">{this.props.author}</p>
  24. <p className="comment-body">
  25. {this.props.body}
  26. </p>
  27. <div className="comment-footer">
  28. <a href="#" className="comment-footer-delete">
  29. Delete comment
  30. </a>
  31. </div>
  32. </div>
  33. );
  34. }
  35. }
  36. class CommentForm extends React.Component {
  37. _handleSubmit(event) {
  38. // Prevent the page from reloading.
  39. event.preventDefault();
  40. // this._author/this._body are populated by refs in JSX
  41. let author = this._author;
  42. let body = this._body;
  43. // Since they are elements, we need to take their ".value".
  44. this.props.addComment(author.value, body.value);
  45. // addComment is called on this.props, meaning it is passed by component parent.
  46. }
  47. render() {
  48. return (
  49. <form className="comment-form" onSubmit={this._handleSubmit.bind(this)}>
  50. <label>Join the discussion</label>
  51. <div className="comment-form-fields">
  52. <input placeholder="Name:"
  53. ref={(input) => { this._author = input; } } /* "this" is CommentForm... *//>
  54. <textarea placeholder="Comment:"
  55. ref={(textarea) => { this._body = textarea; /* ...because of lexical scope */ } } />
  56. </div>
  57. <div className="comment-form-actions">
  58. <button type="submit">Post comment</button>
  59. </div>
  60. </form>
  61. );
  62. }
  63. }
  64. class CommentBox extends React.Component {
  65. constructor() {
  66. super();
  67. this.state = {
  68. showComments: false,
  69. comments: [
  70. { id: 1, author: "Morgan McCircuit", body: "great picture!" },
  71. { id: 2, author: "Bending Bender", body: "Excellent stuff" }
  72. ]
  73. };
  74. }
  75. _addComment(author, body) {
  76. const comment = {
  77. id: this.state.comments.length + 1,
  78. author,
  79. body
  80. };
  81. // concat(), not push(): push() mutates the data, concat doesn't.
  82. // By allocating a new reference, for comments, React detects the change fast.
  83. this.setState({comments: this.state.comments.concat([comment])});
  84. }
  85. _getComments() {
  86. return this.state.comments.map((comment) => {
  87. return (<Comment
  88. author={comment.author}
  89. body={comment.body}
  90. key={comment.id} />);
  91. });
  92. }
  93. _getCommentsTitle(commentCount) {
  94. if (commentCount === 0) {
  95. return "No comments yet";
  96. } else if (commentCount === 1) {
  97. return "1 comment";
  98. }
  99. return `${commentCount} comments`;
  100. }
  101. _handleClick() {
  102. this.setState({
  103. showComments: !this.state.showComments
  104. });
  105. }
  106. render() {
  107. const comments = this._getComments();
  108. let buttonText = "Show comments";
  109. let commentNodes;
  110. if (this.state.showComments) {
  111. commentNodes = <div className="comment-list">{comments}</div>;
  112. buttonText = "Hide comments";
  113. }
  114. return (
  115. <div className="comment-box">
  116. <button onClick={this._handleClick.bind(this)}>{buttonText}</button>
  117. <h4 className="comment-count">{this._getCommentsTitle(comments.length)}</h4>
  118. <CommentForm addComment={this._addComment.bind(this)} />
  119. {commentNodes}
  120. </div>
  121. );
  122. }
  123. }
  124. ReactDOM.render(
  125. <CommentBox />, document.getElementById('comments-app')
  126. );