LoginSignup
5
2

More than 3 years have passed since last update.

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

Posted at

これは何?

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

  • Rails 6.0.3
  • React 17.0.2

作ったもの

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

▼画面イメージと親子関係はこんな感じ
Image from Gyazo

Railsの設定

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

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

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 の部分の書き方を関数コンポーネントというようです。
公式のドキュメントのこちらのページで簡単に比較がしてありましたが、

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) // ココ
  }, [])

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

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を子要素に渡して、フォームで入力した値を一覧画面に表示していきますが、そのために親コンポーネントに少し工夫をします。

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

★2のところで、ステートを管理した[posts, setPosts]二つの関数を、それぞれpropsとして子コンポーネントに渡します。

子コンポーネントを作成

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

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の形で渡す

公式チュートリアルを読む限り、Reactの使用上、子コンポーネントから親コンポーネントのstateを直接書き換えることはできないようです。

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

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

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

PostCreateForm.jsx
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が返ってきます。
本来は、ここでトークンを付与してエラーを解消すべきなのですが、今回は実装時間の関係上、セキュリティ認証を飛ばすというチートを行いました。。。(仕事で作るアプリでは、ちゃんと認証入れます。。。)

/controllers/posts_controller.rb
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も頑張ってみたいと思います。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2