Reactで子コンポーネントのフォームからCreateアクションを実行し、親コンポーネントのデータを更新する。[Rails使用]

## これは何？
Rails + React でSPAのアプリを作っています。途中、子コンポーネントから親コンポーネントにデータを渡す実装がありました。
復習もかね、やったことを記録にまとめます。なお、当方のReactの経験は3日前に初めて触った程度。また、実行環境は下記の通りです。

- Rails 6.0.3
- React 17.0.2

## 作ったもの
基本的なCRUD操作を行うアプリを作っています。`Posts`ページ内にある`CreatePostForm`コンポーネント内で、投稿を作成すると、`Posts`ページに投稿が作成されます。

▼画面イメージと親子関係はこんな感じ
[![Image from Gyazo](https://i.gyazo.com/c4310c406082a6cde9c90bedf060a0ce.png)](https://gyazo.com/c4310c406082a6cde9c90bedf060a0ce)

## Railsの設定
まずはRails側でcreateアクションを実装し、必要なデータをJSON形式で出力できるようにします。

```Ruby:app/controllers/posts_controller.rb
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`というコンポーネント内で作成して、まずは投稿の一覧表示の部分を作っていきます。

```js:Posts.jsx
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 の部分の書き方を**関数コンポーネント**というようです。
公式のドキュメントの[こちらのページ](https://ja.reactjs.org/docs/components-and-props.html)で簡単に比較がしてありましたが、

- [コンポーネントと props](https://ja.reactjs.org/docs/components-and-props.html)

Reactのコンポーネントがより簡潔に書ける書き方のようです。例えば、以下の２つのコードはReactでは同じものを表現しています。

```js
// クラスコンポーネント
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の部分が関数コンポーネントでステートを管理するときの書き方のようです。

```js
const [Posts, setPosts] = useState([])
```

まず、`useState([])`の`()`内にあるのがstateの初期値です。なお、`useState()`メソッドは、同コンポーネントの情報に書いているように、

```js
import React, { useState } from 'react'
```
で関数を読み込んでこないと使えません。
そして、`[Posts, setPosts]`の部分ですが、これはステートの値(Posts)とそれを更新するための関数(setPosts)です。公式の[APIドキュメント](https://ja.reactjs.org/docs/hooks-reference.html#basic-hooks)が詳しかったです。

- [フック API リファレンス - 基本のフック](https://ja.reactjs.org/docs/hooks-reference.html#basic-hooks)

このとき、`Posts`は最初に画面が読み込まれたとき画面に表示する初期値であり、画面が再描画されたときには`Posts`の中身を表示します。

`setPosts`は以下のように新しい値を受け取って、新しい値を受け取って実行されることで、画面を再描画します。

```js
setPosts(newPosts)
```

今回はここの部分で

```js
React.useEffect(async () => {
    const response = await axios.get('/posts');  
    setPosts(response.data) // ココ
  }, [])
```

レスポンスとして帰ってきたデータを受け取って、画面を再描画していますね:relaxed:

### async と await
★3 をつけたこの部分。

```js
React.useEffect(async () => {
    const response = await axios.get('/posts');  // 解説します（★3)
    setPosts(response.data)
  }, [])
```
これは`axios`を使った新しい書き方のようです。

- [axios、async/awaitを使ったHTTPリクエスト(Web APIを実行)](https://qiita.com/shisama/items/61cdcc09dc69fd8d3127)

残念ながら今の私にはJSの知識が不足していて、これ以上詳しくは書けないので、この記事のURLを貼付するに留めておきます。そのうち、知識がつけばしっかり復習したいです。

### keyとmap

最後に、この★4をつけた部分。

```js
{posts.map(post => (
   <li key={post.id}> // ココ
      {post.content}
   </li>
))}
```
Reactでも出てきましたね。これも[公式のドキュメントの記載](https://ja.reactjs.org/docs/lists-and-keys.html#keys)がわかりやすかったですが、要素を削除したときに、Reactがどの要素が削除されたのかわかりやすくするためにつける要素です。

- [keys](https://ja.reactjs.org/docs/lists-and-keys.html#keys)

### 子コンポーネントにデータを渡す準備

さて、これで親コンポーネントで無事データを一覧表示することができました。このあと、親要素で定義した`posts`を子要素に渡して、フォームで入力した値を一覧画面に表示していきますが、そのために親コンポーネントに少し工夫をします。

```js:Posts.jsx
<PostCreateForm posts={posts} setPosts={setPosts} />
```
★2のところで、ステートを管理した`[posts, setPosts]`二つの関数を、それぞれ`props`として子コンポーネントに渡します。

## 子コンポーネントを作成

子コンポーネント`PostCreateForm`に書いたコードはこちらです。

```js:PostCreateForm.jsx
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の形で渡す
公式[チュートリアル](https://ja.reactjs.org/tutorial/tutorial.html#lifting-state-up)を読む限り、Reactの使用上、子コンポーネントから親コンポーネントの`state`を直接書き換えることはできないようです。

そのため、先程記載したように、親コンポーネントから`posts`と`setPosts`をそれぞれpropsとして子コンポーネントに渡し、

```js:Posts.jsx
<PostCreateForm posts={posts} setPosts={setPosts} />
```

子コンポーネントでは、それを`props`として受け取ります。★5

```js:PostCreateForm.jsx
export const PostCreateForm = (props) => { // ココ
 // 略
}
```

### axiosでpostデータを送る
axiosでPOSTリクエストを送信するときは、第２引数で指定したデータがリクエストとして送信されるようです。

```js
axios.post("/posts", { // ここから第二引数
  post: {  
    content: value,
  }
})
```

axiosの使い方は、この記事がわかりやすかったです。

- [axiosの使い方まとめ (GET/POST/例外処理)](https://www.sukerou.com/2019/05/axios.html)

そして、RailsではPOST以外のデータのやりとりに[セキュリティートークン](https://railsguides.jp/security.html#csrf%E3%81%B8%E3%81%AE%E5%AF%BE%E5%BF%9C%E7%AD%96)を付与して、それが一致しないとデータを送信できない仕組みになっています。

今回はRails外でPOSTリクエストを送信しようとしていますので、このままデータを送信すると、

```
Can't verify CSRF token authenticity.
```
と出て、ステータスコード`422`が返ってきます。
本来は、ここでトークンを付与してエラーを解消すべきなのですが、今回は実装時間の関係上、セキュリティ認証を飛ばすというチートを行いました。。。（仕事で作るアプリでは、ちゃんと認証入れます。。。）

```ruby:/controllers/posts_controller.rb
class PostsController < ApplicationController
  # 認証を飛ばすコード
  skip_before_action :verify_authenticity_token, only: %i[create]
end
```

### 親要素のstateを更新する

最後に、リクエストが成功したときに、以下の方法で親要素のstateを更新します。

```js
.then( response => {
   const newPosts = [...props.posts, response.data] // 解説します★7
   props.setPosts(newPosts)
   setValue('')
})
```

`props`として渡ってきた、親要素にあるstateを更新するための関数(`setPosts`)に新しい値`newPosts`をセットし、最後に、フォームを空にしています。

`newPosts`の中身である

```js
[...props.posts, response.data]
```
の`...`の部分は、**「スプレッド演算子」**というらしく、配列や連想配列の要素をまるッと書きたいときに使う構文だそうです。

（以下のコードは、[こちらの記事](https://qiita.com/itizawa/items/075d06fc17f9c5ca5ff7)から引用させていただきました。とてもわかりやすかったです！）

- [Reactにおけるスプレッド構文の使い所](https://qiita.com/itizawa/items/075d06fc17f9c5ca5ff7)

```js
// 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も頑張ってみたいと思います。