React使ってSPAを作るよ(21)

React使ってSPAを作るよ(21)

React使ってSPAを作るよ目次

React使ってSPAを作るよ(20)の続きです。

今回はちょっと大掛かりですが、コメント送信フォームとリストの表示をしたいと思います。

送信用のAPIはいつもどおり作ってもらいました。
まずはこれを$.ajaxで送信するためのフォームコンポーネントを作ります。

//コメント入力欄
var CommentInput = React.createClass({
  handleCommentSubmit: function(){
    $('#spinner').css('display', 'block');
    var articleID = this.props.articleID;
    var comment = this.refs.comment.value;
    $.ajax({
      url: '/api/comment',
      type: 'POST',
      data: {"article_id": articleID, "user_id": loggedin, "comment": comment},
      timeout:10000,
    }).done(function() {
      articleBool = true;
    }).fail(function(xhr, status, err) {
      console.error('/api/comment', status, err.toString());
    });
    this.refs.comment.value = "";
  },
  render: function() {
    return (
      <form className="comment-input" id={'comment-input' + this.props.articleID}>
        <textarea placeholder="コメント" ref="comment"></textarea>
        <button type="button" className="button01" onClick={this.handleCommentSubmit}>コメントを送信</button>
      </form>
    )
  }
});

React使ってSPAを作るよ(17)でやったのと同じように、articleBoolというフラグを立てて、loadArticleFromServer()が実行されるようにしています。
fetchURL()に判定を追記しておきました。

//記事リストコンポーネント
var ArticleArea = React.createClass({

  //JSONデータ取得
  getInitialState: function() {
    return {data: []};
  },
  fetchURL: function() {
    if(apiURL === 'post'){
      apiURL = './api/article';
      tmpURL = apiURL;
      this.loadArticleFromServer();
    }
    else if(apiURL !== tmpURL){
      tmpURL = apiURL;
      this.loadArticleFromServer();
    }
    else if(articleBool === true){
      this.loadArticleFromServer();
    }
  },
//略

・・・なんかもうちょっときれいにまとめて書けそうな気もしますが、処理の違いがわかりやすいようにこのままにしときます。

再レンダリングしたあとは、忘れずにフラグをfalseに戻しておきます。

  loadArticleFromServer: function() {
    var t = this;
    $.ajax({
      url: apiURL,
      dataType: 'json',
      cache: false,
    }).done(function(data) {
      t.setState({data: data});
      $('#spinner').css('display', 'none');
      $('#menu00').prop('checked', true);
    }).fail(function(xhr, status, err) {
      console.error(apiURL, status, err.toString());
      $('#spinner').css('display', 'none');
    });
//追加
    articleBool = false;
  },

すでに投稿されたコメントは「/api/article」で返ってくる記事リストの中に含まれることになります。
React使ってSPAを作るよ(6)で最初につくったJSONのこの部分ですね。

    "commentData": [
      {
       "author": "その1ユーザー1",
       "authorImage": "https://source.unsplash.com/random",
       "comment": "その1コメント1"
      },
      {
       "author": "その1ユーザー2",
       "authorImage": "https://source.unsplash.com/random",
       "comment": "その1コメント2"
      },
      {
       "author": "その1ユーザー3",
       "authorImage": "https://source.unsplash.com/random",
       "comment": "その1コメント3"
      }
    ]
  },

モックの時にはなかったのですが、投稿したユーザー名と日時くらいは出しておきたいな、と思ったので、ここに投稿日時のdateを追加して、コンポーネントは以下のようにしました。

//Comment.jsx

var React = require('react');

//コメント
var Comment = React.createClass({
  render: function() {
  var authorImage = {
    backgroundImage : "url(" + this.props.authorImage + ")" 
  };
    return (
      <li>
        <span data-account={this.props.author} style={authorImage}></span><span><span>{this.props.author}<time>{this.props.date}</time></span>{this.props.comment}</span>
      </li>
    )
  }
});

//コメントリスト
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function(comment) {
      return (
        <Comment author={comment.author} authorImage={comment.authorImage} comment={comment.comment} key={comment.id} />
      )
    });
    return (
      <ul className="comment-list">
        {commentNodes}
      </ul>
    )
  }
});

module.exports = CommentList;

さきほどのコメントフォームとあわせてArticleListの中で呼び出しましょう。
ログインしていないユーザーは投稿できないので、loggedinにユーザーIDが入っているときだけフォームを表示します。

//ArticleArea.jsx

//略
        <h3>コメント</h3>
        <CommentList data={this.props.articleComment} />
        {(() => {
          if (loggedin) {
            return <CommentInput articleID={this.props.articleID} />;
          }
        })()}
//略

こんな感じになりました!

Reactでtextareaの複数行テキストをリストにする

さてこのコメントリストですが、みなさんだいたい困っているようです。

DBに入れるときに変換しても、Reactの実態はJSなので<br />がそのまま表示されてしまいますし、何もしなければ改行されずにテキストノード1個分の謎の空白ができて1行に繋がってしまいます。
だからみんな、mapを使って改行コードごとに要素を区切って表現しているんですね。
しかしこれ、HTML大好きな人間には苦しいですね。

でも大丈夫。
たった1行のCSSが解決してくれます。

  white-space: pre-wrap;

半角スペース・タブ・改行をそのまま表示してくれる指定です。

かんたーんヾ(*´∀`*)ノ

React使ってSPAを作るよ目次