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

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

React使ってSPAを作るよ目次

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

いよいよ記事のURLを送信するところをやりたいと思います。
タグの追加をするときにinput要素にイベントハンドラを設定していたので、それと同じように、送信ボタンを押したら処理が走るようにしましょう。

$.ajaxで送信する設定

URLFormコンポーネントの中で送信ボタンの要素を記述していたので、onClick={this.handleSubmit}を追記します。
そして、handleSubmitの処理を書いていきます。

//URLフォームコンポーネント
var URLForm = React.createClass({
  getInitialState:function(){
    return {tagData:[]};
  },
  handleAddTag:function(tagName){
    var data = this.state.tagData;
    data.push({tagName: tagName});
    this.setState({tagData: data});
  },
  handleSubmit:function(e){
    e.preventDefault();
    inputURL = $('#url-input').val();
    if(inputURL===''){
      alert('URLを入力してください');
      return false;
    }
    tagArr = [];
    $.each($('[name="tag-check"]'),function(){
      if($(this).is(':checked')){
        tagArr.push($(this).attr('id'));
      }
    });
    $.ajax({
      url: './api/article',
      type: 'POST',
      data: {"url": inputURL, "tag": tagArr},
      timeout:10000,
      })
      .done(function() {
        $('#url-input').val('');
      })
      .fail(function(xhr, status, err) {
        alert(err);
      });
  },
  render:function(){
    return(
    <form id="form-url-input" className="wrap">
      <input type="url" placeholder="URL" id="url-input" required="required"/><input type="submit" id="url-submit" onClick={this.handleSubmit}/>
      <TagForm addTag={this.handleAddTag} tagData={this.state.tagData} />
    </form>
    );
  }
});

もともと$.ajaxでJSONデータをGETしていたので、それをコピーして使ってみます。

送信先のURLは自分の環境に合わせてくださいね。
PHPでもNode.jsでも、好きな言語で実装していいと思います。

私の場合は、React使ってSPAを作るよ(8)で同僚が作ってくれたAPI(PHP)があるので、そこにデータを渡します。

今回は送信するのでtypeはPOSTに変更。
timeoutも設定しておきました。

そして、ここで盛大にハマりました。
もともとdataTypeにjsonを指定していましたよね。
その状態で送信ボタンを押してみると、

Uncaught SyntaxError: Unexpected token o

このエラーが出ます。
なんだろな、と思って調べてみると、oというのはobjectのことで、つまりJSONオブジェクトを渡そうとしているのでエラーになっているわけです。
はて…
それでなにがいけないんでしょうか???

問題は、dataの中身。

      data: {"url": inputURL, "tag": tagArr},

これだと、データを送る時点でJSONオブジェクトになっています。
そうすると、dataの中身をJSONに変換しようとして、json.parseしようとするんですが、もうすでにJSONオブジェクトなのでparseがうまくいかない!!という事態になるようです。

そこで、dataType: ‘json’の指定を外すことでdataを文字列として渡し、そのあとでそれをjson.parseしてもらうという…なんだか微妙に遠回りなやり方になりました。
どうせJSONオブジェクトで渡すんだから、parseしなきゃいいのに…と思うんですが、PHP側の処理はまだ手を出せないのでこのままにしておきます(;´∀`)

handleSubmitの中身を解説

さて、$.ajaxの中身について気を付けるところは以上ですが、送信ボタンをクリックしたときの処理を順番どおりに説明していきます。

  handleSubmit:function(e){
    e.preventDefault();

jQuery使っててもしょっちゅう出てくるんですが、onClickの本来持っているイベントをキャンセルします。
遷移しないためにせっかく$.ajaxを使ってるのに、input[type=”submit”]をクリックしたことで勝手に画面がリフレッシュされるのを防ぎます。

続いて、URL入力欄の中身をチェックします。

    inputURL = $('#url-input').val();
    if(inputURL===''){
      alert('URLを入力してください');
      return false;
    }

空になっていたらその時点ですぐに処理を抜けます。
このalert()は無骨すぎるので、あとでトースト表示かなにか、自分でデザインした表示に変えたいですね。

入力値のバリデーションはあとで実装していきたいと思います。
サーバサイドになるとは思いますが…

で、URLが入力されていれば処理を進めます。

    tagArr = [];
    $.each($('[name="tag-check"]'),function(){
      if($(this).is(':checked')){
        tagArr.push($(this).attr('id'));
      }
    });

タグのチェックボックスの中でチェックされているものを配列に入れていきます。
このtagArrが、$.ajaxで送信するdataの中に入ってますね。

無事URLが送信できれば.done()で入力欄を空にし、それ以外の場合はエラーを表示しています。

Reactらしく、Reactiveなレンダリングを

これでなんとかURLとタグを登録することができるようになりました。
でも、送信したあとに更新しないと画面に反映されません。
(´ヘ`;)ウーム…
これじゃ、面白くないですね。

せっかくですから、JSON(DB)の中身が変わったら自動で画面に反映するようにしたいです。

それには、URLFormコンポーネントではなく、記事リストを表示する側、ArticleAreaコンポーネントを修正します。

本家のチュートリアルにこの例があるので参考にしました。

さて、componentDidMount はコンポーネントがレンダリングされたときに React が自動的に呼び出すメソッドです。動的な更新の鍵となるのは this.setState() の呼び出し方です。ここでは、古いコメントの配列をサーバから取ってきた新しい配列に置き換え、UI を自動的に更新させてみましょう。このような reactivity(反応性・柔軟性)のおかげで、リアルタイム更新を最小限にすることが出来ます。

ここらへんまでくると、まさにReactを活用してる感じになりますね!

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

ArticleArea.jsxファイルのこのコンポーネントを見てください。

以下のように書き換えました。

  //JSONデータ取得
  getInitialState: function() {
    return {data: []};
  },
  loadArticleFromServer: function() {
    var t = this;
    $.ajax({
      url: './api/article',
      dataType: 'json',
      cache: false,
    }).done(function(data) {
      t.setState({data: data});
    }).fail(function(xhr, status, err) {
      console.error('./api/article', status, err.toString());
    });
  },
  componentDidMount: function() {
    this.loadArticleFromServer();
    setInterval(this.loadArticleFromServer, 2000);
  },

今まで最初のレンダリング時にJSONを直接読み込んでいましたが、これをloadArticleFromServerというfunctionにしてしまいます。
componentDidMountでは、初回の読み込みのほかに、setIntervalで2秒に1回、読み込みを行うようにしました。
これで、もしデータが変わっていたら反映されるようになります。

…なんていうか…

本当は、URL送信が成功した時点で1回だけできればいいんですけど…
閲覧している間、2秒に1回ずーっとデータ取りにいくなんて…

とか思っていました。

でも、これって、サーバ上のデータが変わったら勝手に更新してくれるわけで、つまり、他人がURL送信して新しい記事が増えたら自分の画面にも自動で反映されるわけですよね!

おお~( ゚д゚ )

Socket通信のようなリアルタイム性とはちょっと異なりますが、チャットではないので十分です。
むしろこれ、コメント機能をチャットぽく使うこともできてしまいますね。
いろいろ可能性が広がってきました。

今は各自ローカルで作業しているので試せないのですが、検証用のサーバ環境整ってデプロイしたら複数人で試してみたいです。

気になるところ

機能としてはだいぶ使えるようになってきましたが、人に使ってもらうにはまだまだ、ですね。

たとえば、URLを送信している間、遷移もしないので画面に変化がありません。
これは送信ボタンを押したときにスピナーを表示するなど、UIに手を入れたいです。

それから、今はJSONデータをそのまま持ってきているので新しい記事が下に追加されちゃいます。
これも、昇順降順の切り替えをしたいですが…
やり方としては以下の3つのどれかかな?

・送信したときにJSONの上に追加していく
・APIから返すときに新しい順にする
・返ってきたデータを新しい順にソートする

どれが一番いいんでしょうね?

あと、送信が終わった後はフォームのタグ欄は閉じたいですね(;´∀`)
このへんの細かいUIの調整していかないと!

React使ってSPAを作るよ目次