React + RailsのSPAでdestroyアクションを実装する


## これは何？
React+Railsで簡単なタスク管理アプリを作っています。
ラベルの隣にある「削除」ボタンを押すと、確認メッセージののち、ラベルが削除されます。

[![Image from Gyazo](https://i.gyazo.com/1f94c96ccd134da65e46b8e581b89ba5.gif)](https://gyazo.com/1f94c96ccd134da65e46b8e581b89ba5)

実行環境は以下の通りです。

- Rails 6.0.3
- React 17.0.2
また、今回のディレクトリ構成は以下の通りです。(関係のある箇所だけ表示)

```sh
.
├── controllers
│   └── api
│       └── labels_controller.rb
└── javascript
    └── pages
        └── Labels.jsx
```

## Rails側
ある意味衝撃でした。詳細確認中ですが、`controller`にも`routes`にもdestroy関連アクションを何も記載しなくても動作しました。(axiosのためかしら。。。。)

## SPA側

削除メソッド自体はとてもシンプルで、`axios.delete(URL[, オプション])`で渡せばOKでした。その他、実装においてスラスラといかなかったところは解説します。

```js:
import React, { useState } from 'react'
import axios from 'axios'

export const Labels = (props) => {
  const [labels, setLabels] = useState([])

  React.useEffect(async () => {
    const response = await axios.get('/api/labels');
    setLabels(response.data)
  }, [])

  const removeLabel = (labelId, e) => {
    e.preventDefault();
    if (window.confirm("ラベルを削除します。よろしいですか?")) {
      axios.delete("/labels/" + labelId, {
        headers: {  // ★１解説します
          'X-Requested-With': 'XMLHttpRequest',
          'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
        }
      })
      .then( res => {  // ★3解説します
        const targetIndex = labels.findIndex( label => {
          return label.id === res.data.id
        })
        const newLabels = labels.slice();
        newLabels.splice(targetIndex, 1);
        setLabels(newLabels)
      })
      .catch(data => {
        console.log(data);
      })
    }
  }
  return (
    <div>
      <h1>ラベル一覧</h1>
        // 略
      <h3>ラベル</h3>
      <ul>
        {labels.map(label => (
          <li key={label.id}>
            {label.name}
            <a href="" onClick={(e) => removeLabel(label.id, e)}>削除</a> // ★2解説します
          </li>
        ))}
      </ul>
    </div>
  )
}

```

### CSRFトークン
★１の部分です。RailsではGET以外のajaxリクエストにデフォルトで、ヘッダーにセキュリティトークンを付与します（詳細は[Railsセキュリティガイド](https://railsguides.jp/security.html#csrf%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C%E7%AD%96)を参照）。

```js
axios.delete("/labels/" + labelId, {
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
     }
   })
```
AjaxでGET以外のリクエストを実施するときには、リクエストヘッダーの`X-CSRF-Token`の項に、トークンを付与する必要があります。

トークンは、`<head>`タグ内、`<meta name="csrf-token" ...>`の`content`に記載されています。これは別のRailsアプリの画像ですが、該当箇所にトークンがありますね。
[![Image from Gyazo](https://i.gyazo.com/11b926032d3e4d6e5dc14be004620822.png)](https://gyazo.com/11b926032d3e4d6e5dc14be004620822)

トークンの上の行にある、こちらの記載は詳しく調べきれていないのですが、

```js
'X-Requested-With': 'XMLHttpRequest',
```

[こちらの記事](https://qiita.com/_y_s_k_w/items/cc331df8edf6ecd5d252)によると、

> また、クロスドメインの場合、headerに'X-Requested-With' : 'XMLHttpRequest'を入れて送信しなければ、api側でAjaxであることが伝わらない...

引用：[クロスドメインで'X-Requested-With'の設定](https://qiita.com/_y_s_k_w/items/cc331df8edf6ecd5d252)
とのことだったので、RailsにAjaxのリクエストであることを伝えるコードなのだと今は理解しておきます。

### onClickイベントの渡し方
★2の部分です。「削除」のリンクがある箇所です。

```js
<a href="" onClick={(e) => removeLabel(label.id, e)}>削除</a>
```
大したことではないのですが、[Reactチュートリアル](https://ja.reactjs.org/tutorial/tutorial.html#making-an-interactive-component)にこのような記載もあるくらい、間違えやすい箇所でしたので、備忘も兼ね記載しておきます。

> onClick={() => alert('click')} と記載したときに onClick プロパティに渡しているのは関数であることに注意してください。React はクリックされるまでこの関数を実行しません。() => を書くのを忘れて onClick={alert('click')} と書くのはよくある間違いであり、こうするとコンポーネントが再レンダーされるたびにアラートが表示されてしまいます。

JSの式を渡さずに、関数を渡すように心がけます。

### findIndex とか splice とか
★3の部分です。ここも難しいことはしていないのですが、私がJSに詳しくないので、勉強も兼ねての記載です。

なお、ロジックそのものは[こちらの記事](https://zenn.dev/tatsurom/articles/0b212d24c80c334d70c9#destroy(%E5%89%8A%E9%99%A4))のものを利用させていただきました。

- [React + Rails API モードで基本的な CRUD アプリを作ってみた (フロントエンド編 その2)](https://zenn.dev/tatsurom/articles/0b212d24c80c334d70c9#destroy(%E5%89%8A%E9%99%A4))

```js
.then( res => {
  const targetIndex = labels.findIndex( label => {
    return label.id === res.data.id
  })
  const newLabels = labels.slice();
  newLabels.splice(targetIndex, 1);
  setLabels(newLabels)
})
```

- [findIndex](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) ... **配列.findIndex(条件)**の形で、条件を満たす最初の要素のインデックス番号を返します。条件を満たすものがない場合は`-1`を返します。


- [splice](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) ... **配列.splice(スタート位置[, 削除する数])**の形で、スタート位置（インデックス番号）から指定の数の要素を取り除きます。

## 完成！
以上で、削除機能の実装は完了です。次回は、「１回１回フォームにリクエストを書くのは面倒なので、まとめて一括で設定する」に取り組んでみたいです。

色々やったけどうまくいかなかった。。。。
また、アップデートアクションを作ったら続きを記載します。