これは何?
React+Railsで簡単なタスク管理アプリを作っています。
ラベルの隣にある「削除」ボタンを押すと、確認メッセージののち、ラベルが削除されます。
実行環境は以下の通りです。
- Rails 6.0.3
- React 17.0.2
また、今回のディレクトリ構成は以下の通りです。(関係のある箇所だけ表示)
.
├── controllers
│ └── api
│ └── labels_controller.rb
└── javascript
└── pages
└── Labels.jsx
Rails側
ある意味衝撃でした。詳細確認中ですが、controller
にもroutes
にもdestroy関連アクションを何も記載しなくても動作しました。(axiosのためかしら。。。。)
SPA側
削除メソッド自体はとてもシンプルで、axios.delete(URL[, オプション])
で渡せばOKでした。その他、実装においてスラスラといかなかったところは解説します。
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: { // ★1解説します
'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トークン
★1の部分です。RailsではGET以外のajaxリクエストにデフォルトで、ヘッダーにセキュリティトークンを付与します(詳細はRailsセキュリティガイドを参照)。
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アプリの画像ですが、該当箇所にトークンがありますね。
トークンの上の行にある、こちらの記載は詳しく調べきれていないのですが、
'X-Requested-With': 'XMLHttpRequest',
こちらの記事によると、
また、クロスドメインの場合、headerに'X-Requested-With' : 'XMLHttpRequest'を入れて送信しなければ、api側でAjaxであることが伝わらない...
引用:クロスドメインで'X-Requested-With'の設定
とのことだったので、RailsにAjaxのリクエストであることを伝えるコードなのだと今は理解しておきます。
onClickイベントの渡し方
★2の部分です。「削除」のリンクがある箇所です。
<a href="" onClick={(e) => removeLabel(label.id, e)}>削除</a>
大したことではないのですが、Reactチュートリアルにこのような記載もあるくらい、間違えやすい箇所でしたので、備忘も兼ね記載しておきます。
onClick={() => alert('click')} と記載したときに onClick プロパティに渡しているのは関数であることに注意してください。React はクリックされるまでこの関数を実行しません。() => を書くのを忘れて onClick={alert('click')} と書くのはよくある間違いであり、こうするとコンポーネントが再レンダーされるたびにアラートが表示されてしまいます。
JSの式を渡さずに、関数を渡すように心がけます。
findIndex とか splice とか
★3の部分です。ここも難しいことはしていないのですが、私が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 ... **配列.findIndex(条件)**の形で、条件を満たす最初の要素のインデックス番号を返します。条件を満たすものがない場合は
-1
を返します。 -
splice ... **配列.splice(スタート位置[, 削除する数])**の形で、スタート位置(インデックス番号)から指定の数の要素を取り除きます。
完成!
以上で、削除機能の実装は完了です。次回は、「1回1回フォームにリクエストを書くのは面倒なので、まとめて一括で設定する」に取り組んでみたいです。
色々やったけどうまくいかなかった。。。。
また、アップデートアクションを作ったら続きを記載します。