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;
半角スペース・タブ・改行をそのまま表示してくれる指定です。
かんたーんヾ(*´∀`*)ノ