【jQuery】初学者の自分がAjaxでハマったポイント

# はじめに

RailsでAjaxを実装するとなり、ほぼ初めてJavaScriptに触りました。
そんな私が理解に苦しんだところやハマったポイントをまとめました。

## 実装するもの

Ajax関数による非同期通信でコメントの編集をするというものです。
以下のような流れになります。

編集ボタンを押す
　→編集フォームが現れる
編集中、キャンセルボタンを押す
　→変更せずにコメントを表示
編集後、更新ボタンを押す
　→データを更新して、編集後のコメントを表示する
　→不適な場合（バリデーションエラー）、エラーメッセージを表示し、編集フォームはそのままにする

# そもそもAjaxの処理の流れってどうなってんの

基本的に、イベントの中にAjax関数を入れることになります。
Ajax関数の役割ですが、

> HTTP通信でページを読み込みます。
[jQuery日本語リファレンス](http://semooh.jp/jquery/api/ajax/jQuery.ajax/options/)

> Ajaxリクエストを送信するオプションをキーと値のペアで指定します。
[JavaScript 日本語リファレンス](http://js.studio-kingdom.com/jquery/ajax/ajax)

らしいです。これだけを見ると、初学者からすると非常にわかりづらいので、順々に追っています。

```javascript
$.ajax({
    type: 'PATCH', // HTTP通信の種類
    url: 'comments/:id', // データの送り先のURL ※idが書いていないのでこのままでは動きません
    data: { // サーバーへ送信するデータ
        body: 'コメント'
    }
}).done(function () {
    // 成功処理
}).fail(function (result) {
    // 失敗処理
});
```

typeとurlは`$ rails routes`ですぐにわかります。
Ajax関数がtypeとurlから`PATCH comments/:id`にリスエストとして送り、ルーティングを通ってコントローラのアクションへdataを送ります。
Ajaxによって送られたデータはコントローラ側では`params`で受け取れます。

``` rb:comments_controller.rb
  def update
    @comment = current_user.comments.find(params[:id])
    if @comment.update(body: params[:body]) # 'コメント'を受け取る
      render status: 200
    else
      render json: { comment: @comment, errors: { messages: @comment.errors.full_messages } }, status: 422
    end
  end
```

コントローラがrenderによって返したデータは`.done(function (result)`の`result`に格納されます。
statusは`result.status`、jsonは`result.responseJSON`で取得できます。

簡単に図に表すとこんな感じです。
![Untitled Diagram.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322882/4af771a7-fc5c-84fd-027b-da78256f3450.jpeg)

コントローラ側が特に何も返さなかったり、`status: 200`などを返したときはdoneへ、エラーや`status: 400`などを返したときはfailへ分岐します。

この辺りのイメージは、[イラスト図解式 この一冊で全部わかるWeb技術の基本](https://www.amazon.co.jp/gp/product/4797388811)のChapter3を読むと理解が深まりました。

# ダメなパターン

では、私がやらかしたことを以下に書きます。

## status: 422を返さない（コントローラ側）

```rb:comments_controller.rb
# ダメパターン
  def update
      render json: { comment: @comment, errors: { messages: @comment.errors.full_messages } } unless @comment.update(body: params[:body])
  end
```

`update`では保存に失敗したときに`false`を返すだけでステータスを返さないため、js側では成功として認識します。そのため、`.done`の方に処理が流れてしまいます。
`update!`に変更するか（自動的に例外を返してくれる）、renderで明示的にstatusを返すようにすると、ちゃんと`.fail`に流れてくれます。

ちなみに、[headでヘッダのみのレスポンスを生成する](https://railsguides.jp/layouts_and_rendering.html#head%E3%81%A7%E3%83%98%E3%83%83%E3%83%80%E3%81%AE%E3%81%BF%E3%81%AE%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B9%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B)こともできるので、上の`render status: 200`は`head :created`と書くこともできます。

## formとAjax関数で2つのリクエストを送ってしまう（js側）

```js
// ダメパターン
$(document).on("click", ".update-button", function(){
  $.ajax({
    // 省略
  });
});
```
このようにした場合、更新ボタンを押すと、

- フォームから送信されたデータ（通常のform_withのsubmitによるもの）
- Ajax関数によって送信されたデータ

と二重に送られてしまいます。

```js
// オッケーパターン
$(document).on("click", ".js-comment-update-button", function(e){
  e.preventDefault();
  $.ajax({
    // 省略
  });
});
```

このように[e.preventDefault()](https://developer.mozilla.org/ja/docs/Web/API/Event/preventDefault)と書くことで、通常の動作を制御することができます。

- [preventDefault()を使うための前提知識](https://qiita.com/tochiji/items/4e9e64cabc0a1cd7a1ae)

## 独立したイベントを入れ子にしてしまう（js側）

このときの私の思考：送信ボタンを押すというイベントは編集ボタンをクリックした後じゃないと実行しないから、この中に入れよう。
これに関しては、私のjsの理解度の低さが浮き彫りとなったミスだと思います。

```javascript

$(function() {
  // 編集ボタンを押したとき
  $(document).on("click", ".edit-button", function() {
    // 処理A

    // 送信時
    $(document).on('click', '.update-button', function(){
      // 処理B
    });
  });
});
```

何がダメかというと、送信ボタンを押した後も処理Aが終了しないのです。
図に示すと、このような入れ子型になります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322882/84c4527c-dcbb-a98f-189e-644be27d9fba.jpeg" width="300">

```javascript

$(function() {
  $(document).on("click", ".edit-button", function() {
    // 処理A

    $(document).on('click', '.update-button', function(){
      // 処理B
    });
    // ここで止まってしまい、処理Aから抜けられない
  });
});
```

そのため、再び編集ボタンを押したときには、新たな処理Aが実行されます。その後更新ボタンを押すと、処理Bが2回実行されることになります。
（もちろん、その後同じことを繰り返すと3回、4回、……とどんどん膨れ上がります）

というわけで、一度のイベントで行われる処理同士は入れ子にせず、並列させるようにしましょう。

# 完成したコード

省略しているところもありますが、こんな感じです。

```javascript
$(function() {
  // 編集ボタンを押したとき
  $(document).on('click', '.edit-button', function() {
    // 処理
  });

  // キャンセルしたとき
  .on('click', '.cancel-button', function() {
    // 処理
  });

  // 送信したとき
  .on('click', '.update-button', function(e){
    e.preventDefault();
    const commentId = $(this).data("comment-id");
    const editBody = $('textarea#comment-' + commentId).val();
    //処理
    $.ajax({
      type: 'PATCH',
      url: 'comments/' + commentId,
      data: {
        body: editBody
      }
    }).done(function () {
      // 成功処理
    }).fail(function () {
      // 失敗処理
    });
  });
});
```

ちなみに、先述の並列の処理（編集ボタンを押したとき/キャンセルしたとき/送信したとき）は上のように`.on`で繋げることができるそうです。

# おわりに

自分なりにまとめてみましたが、足りないところ・間違っているところなどがございましたらコメントでご指摘をお願いします。