これは何?
Rails + React でSPAのアプリを作っています。途中、子コンポーネントから親コンポーネントにデータを渡す実装がありました。
復習もかね、やったことを記録にまとめます。なお、当方のReactの経験は3日前に初めて触った程度。また、実行環境は下記の通りです。
- Rails 6.0.3
- React 17.0.2
作ったもの
基本的なCRUD操作を行うアプリを作っています。Posts
ページ内にあるCreatePostForm
コンポーネント内で、投稿を作成すると、Posts
ページに投稿が作成されます。
Railsの設定
まずはRails側でcreateアクションを実装し、必要なデータをJSON形式で出力できるようにします。
class PostsController < ApplicationController
skip_before_action :verify_authenticity_token, only: %i[create]
def index
@posts = Post.all
render json: @posts
end
def create
post = Post.build(post_params)
if post.save
render json: post
else
lender json: post.errors
end
end
private
def post_params
params.require(:label).permit(:name)
end
end
親コンポーネントを作成
次に、親コンポーネントであるPostsを記載していきます。今回はapp/javascript/pages/Posts.jsx
というディレクトリ下に作成しました。
フォーム要素はPostCreateForm
というコンポーネント内で作成して、まずは投稿の一覧表示の部分を作っていきます。
import React, { useState } from 'react'
import { PostCreateForm } from '~/components/PostCreateForm'
import axios from 'axios'
export const Posts = () => { // 解説します(★1)
const [Posts, setPosts] = useState([]) // 解説します(★2)
React.useEffect(async () => {
const response = await axios.get('/posts'); // 解説します(★3)
setPosts(response.data)
}, [])
return (
<div>
<h1>投稿一覧</h1>
<PostCreateForm /> // これが子コンポーネント
<h3>作成した投稿</h3>
<ul>
{posts.map(post => (
<li key={post.id}> // 解説します(★4)
{post.content}
</li>
))}
</ul>
</div>
)
}
関数コンポーネント
まず★1 の部分の書き方を関数コンポーネントというようです。
公式のドキュメントのこちらのページで簡単に比較がしてありましたが、
Reactのコンポーネントがより簡潔に書ける書き方のようです。例えば、以下の2つのコードはReactでは同じものを表現しています。
// クラスコンポーネント
class Content extends React.Component {
constructor(props){
super(props)
this.state = {
content: ''
}
}
render() {
return <p>{this.props.content}</p>;
}
}
// 関数コンポーネント
export const Content = () => {
const [content, setContent] = useState('')
return (
return <p>{content}</p>;
)
}
ざっと調べた限りでは、これまで、state
やライフサイクルフックなどの主要機能がクラスコンポーネントでしか使えなかったため、クラスコンポーネントでの書き方が多かったようですが、
最近hook
という機能が導入され、関数コンポーネントでも同様の書き方ができるようになったとのことでした。
関数コンポーネントでのステートの管理
そして、★2の部分が関数コンポーネントでステートを管理するときの書き方のようです。
const [Posts, setPosts] = useState([])
まず、useState([])
の()
内にあるのがstateの初期値です。なお、useState()
メソッドは、同コンポーネントの情報に書いているように、
import React, { useState } from 'react'
で関数を読み込んでこないと使えません。
そして、[Posts, setPosts]
の部分ですが、これはステートの値(Posts)とそれを更新するための関数(setPosts)です。公式のAPIドキュメントが詳しかったです。
このとき、Posts
は最初に画面が読み込まれたとき画面に表示する初期値であり、画面が再描画されたときにはPosts
の中身を表示します。
setPosts
は以下のように新しい値を受け取って、新しい値を受け取って実行されることで、画面を再描画します。
setPosts(newPosts)
今回はここの部分で
React.useEffect(async () => {
const response = await axios.get('/posts');
setPosts(response.data) // ココ
}, [])
レスポンスとして帰ってきたデータを受け取って、画面を再描画していますね
async と await
★3 をつけたこの部分。
React.useEffect(async () => {
const response = await axios.get('/posts'); // 解説します(★3)
setPosts(response.data)
}, [])
これはaxios
を使った新しい書き方のようです。
残念ながら今の私にはJSの知識が不足していて、これ以上詳しくは書けないので、この記事のURLを貼付するに留めておきます。そのうち、知識がつけばしっかり復習したいです。
keyとmap
最後に、この★4をつけた部分。
{posts.map(post => (
<li key={post.id}> // ココ
{post.content}
</li>
))}
Reactでも出てきましたね。これも公式のドキュメントの記載がわかりやすかったですが、要素を削除したときに、Reactがどの要素が削除されたのかわかりやすくするためにつける要素です。
子コンポーネントにデータを渡す準備
さて、これで親コンポーネントで無事データを一覧表示することができました。このあと、親要素で定義したposts
を子要素に渡して、フォームで入力した値を一覧画面に表示していきますが、そのために親コンポーネントに少し工夫をします。
<PostCreateForm posts={posts} setPosts={setPosts} />
★2のところで、ステートを管理した[posts, setPosts]
二つの関数を、それぞれprops
として子コンポーネントに渡します。
子コンポーネントを作成
子コンポーネントPostCreateForm
に書いたコードはこちらです。
import React, { useState } from 'react'
import axios from 'axios'
export const PostCreateForm = (props) => { // 解説します★5
const [value, setValue] = useState('')
const handleSubmit = (e) => {
e.preventDefault();
if(value){
axios.post("/posts", { // 解説します★6
post: {
content: value,
}
})
.then( response => {
const newPosts = [...props.posts, response.data] // 解説します★7
props.setPosts(newPosts)
setValue('')
})
.catch( error => {
console.log(error)
})
} else {
alert('内容を入力してください');
}
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={event => setValue(event.target.value)}
/>
<input type="submit" value="投稿" />
</form>
</div>
)
}
親のステートは子にpropsの形で渡す
公式チュートリアルを読む限り、Reactの使用上、子コンポーネントから親コンポーネントのstate
を直接書き換えることはできないようです。
そのため、先程記載したように、親コンポーネントからposts
とsetPosts
をそれぞれpropsとして子コンポーネントに渡し、
<PostCreateForm posts={posts} setPosts={setPosts} />
子コンポーネントでは、それをprops
として受け取ります。★5
export const PostCreateForm = (props) => { // ココ
// 略
}
axiosでpostデータを送る
axiosでPOSTリクエストを送信するときは、第2引数で指定したデータがリクエストとして送信されるようです。
axios.post("/posts", { // ここから第二引数
post: {
content: value,
}
})
axiosの使い方は、この記事がわかりやすかったです。
そして、RailsではPOST以外のデータのやりとりにセキュリティートークンを付与して、それが一致しないとデータを送信できない仕組みになっています。
今回はRails外でPOSTリクエストを送信しようとしていますので、このままデータを送信すると、
Can't verify CSRF token authenticity.
と出て、ステータスコード422
が返ってきます。
本来は、ここでトークンを付与してエラーを解消すべきなのですが、今回は実装時間の関係上、セキュリティ認証を飛ばすというチートを行いました。。。(仕事で作るアプリでは、ちゃんと認証入れます。。。)
class PostsController < ApplicationController
# 認証を飛ばすコード
skip_before_action :verify_authenticity_token, only: %i[create]
end
親要素のstateを更新する
最後に、リクエストが成功したときに、以下の方法で親要素のstateを更新します。
.then( response => {
const newPosts = [...props.posts, response.data] // 解説します★7
props.setPosts(newPosts)
setValue('')
})
props
として渡ってきた、親要素にあるstateを更新するための関数(setPosts
)に新しい値newPosts
をセットし、最後に、フォームを空にしています。
newPosts
の中身である
[...props.posts, response.data]
の...
の部分は、**「スプレッド演算子」**というらしく、配列や連想配列の要素をまるッと書きたいときに使う構文だそうです。
(以下のコードは、こちらの記事から引用させていただきました。とてもわかりやすかったです!)
// Array
const odd = [1, 3]
const even = [2, 4]
const numbers = [...odd, ...even]
console.log(numbers) // [1, 3, 2, 4]
// Object
const name = {first: "Tanaka", last: "Taro"}
const age = {age: 27}
const profile = {...name, ...age}
console.log(profile) // {first: "Tanaka", last: "Taro", age: 27}
今はちょっとできる環境にないのですが、後ほど、...props.posts
にどんなデータが入っているか確かめてみたいと思います。
完成!
これで、Reactでデータを更新し、一覧表示するフォームができました!
CRUDのCとRができたところになりますので、次回以降UとDも頑張ってみたいと思います。