フロントReact、バックRailsでデータベースに保存する
最近Reactを色々やってみて、ふと「DBに保存するにはどうすれば良いのだろう?」と言う疑問が沸き調べたところReact単体でDBに保存するのはできないと言うことがわかりました。
ReactとはNode.jsとつなげた方がJS同士だからいいんだろうけど、慣れているRailsをバックにしてみます。
調べたことをアウトプットしながら書いていくのでコードだけ欲しい!みたいな人には冗長に感じるかもですがご愛敬。
RailsをAPIとして使う
同じ初学者の人だとそもそも「RailsをAPIで使うってどう言うこっちゃ?」ってなると思います。
自分もなりました。
Javascriptの入門本とかだとポピュラーなのはウェザーAPIだし
有名どころで言えばGoogleAPIとかYoutubeAPIとかですね。
そもそもAPIってなんぞ?と言うことです
以下のようなことらしいです
「API」とは、「Application Programming Interface」の頭文字です。
英文字だけで意味を理解しようとすると、アプリケーション・プログラミング・インターフェイスで、大雑把に言うと「アプリケーションをプログラミングするためのインターフェース」という意味です。
インターフェイスとは、コンピュータ用語でいうと、「何か」と「何か」をつなぐものという意味を持ちます。例えば、USBも「パソコン」と「周辺機器」をつなぐものですので、インターフェイスの一つです。
つまり、APIとは、この「何か」と「何か」が「アプリケーション、ソフトウェア」と「プラグラム」をつなぐもの、という意味になります。
つまりRailsでフロントとサーバーを繋ごうと言うことになるのかな
viewは完全にReactで作り、Railsにデータを渡してそこからDBにと言う感じですね
実際に作っていきます
RailsをAPIとして作る
こちらが大変参考になりました
Ruby on Rails+ReactでCRUDを実装してみた
この記事を参考に進めます
まずはrails newです
お世話になっているRailsガイドに書いてあります
Rails による API 専用アプリケーション
API専用Railsアプリケーションの生成には次のコマンドを使います。
$ rails new my_api --api
--api をオプションでつけることでRails をAPI専用で作成できます
作られたアプリに移動したらモデルを作ります
$ rails g model user name:string $ rails db:migrate
DBのデータがやりとりできるか確かめるためにテストデータを作成します
User.create([ {name: "鈴木"}, {name: "田中"}, {name: "佐藤"} ])
rails db:seed
コントローラを作成
$ rails g controller users
class UsersController < ApplicationController def index @users = User.all render json: @users end def create @user = User.create(name: params[:name]) render json: @user end def update @user = User.find(params[:id]) @user.update(name: params[:name]) render json: @user end def destroy @user = User.find(params[:id]) if @user.destroy head :no_content, status: :ok # headメソッド...本文(body)のないレスポンスをブラウザに送信できる # :no_content...レンダリングしようとすると、レスポンスから削除される # status: :ok...リクエストに成功 else render json: @user.errors, stateus: :unprocessable_entity # stateus: :unprocessable_entity...バリデーションエラー end end end
render json: オブジェクト とすることでjson形式でオブジェクトが返されます
個人的によくわからない所はコメントをつけてあります
次にルーティングを追加
Rails.application.routes.draw do resources :users end
ここまでできたらRailsのポートを立ち上げてデータがjson形式で帰ってくるか確かめます
Reactで3000番ポート、Railsで3001番ポートを使うので、3001番で立ち上げます
$ rails s -p 3001
立ち上げた状態でもう一つターミナルを立ち上げてアクセスします
$ curl -G http://localhost:3001/products/
[{"id":1,"name":"鈴木","created_at":"2020-12-08T10:07:40.549Z","updated_at":"2020-12-08T10:07:40.549Z"},{"id":2,"name":"田中","created_at":"2020-12-08T10:07:40.553Z","updated_at":"2020-12-08T10:07:40.553Z"},{"id":3,"name":"佐藤","created_at":"2020-12-08T10:07:40.556Z","updated_at":"2020-12-08T10:07:40.556Z"}]
このように、ターミナルにデータが帰ってくれば成功です
しかしここで1つ問題があります
Reactで3000番ポート、Railsで3001番ポートを使う、と言うことにしました
現状、ポートの番号が違う方にアクセスしようとするとエラーがでます
APIとしてデータをやりとりしようとするRailsが、React側の3000番にアクセスできるようにします
gem 'rack-cors'を使います
こちらが参考になりました
【Rails6】Gem rack-corsを導入してCORS設定を行う(オリジン・CORSとは何か)
このgemはrailsに元々コメントとして入っているので、外して導入しましょう
gem 'rack-cors'
$ bundle install
cofig/initializers に cors.rb と言うファイルができますが、config/application で内容を記述します
module TestApp class Application < Rails::Application config.load_defaults 5.2 config.api_only = true config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3000' resource '*', :headers => :any, :methods => [:get, :post, :patch, :delete, :options] end end end end
Rails側の設定ができました
React側を作る
すでにnodeが導入されていればnpmのcreat-react-appを導入します
nodeが入っていない場合はまずnodeを導入しましょう
(検索すると色々でてきます。自分はprogateですでに入れてました)
$ npm i -g create-react-app
今回一枚ものでviewを作るのでfrontと言う名前でreactを構築します
$ create-react-app front
作ったfrontに移動してnpm startでReactが起動します
いきなりブラウザにでてくるかと思います
この時点でアドレスはhttp://localhost:3000/
http://localhost:3001/にすると、起動させていればRailsが開けます
非同期のHTTP通信を簡単にする axios と言うものを導入します
APIとDBのやりとりを簡単に実装できるものらしいです
$ npm i axios
次にApp.jsを変更してメインとなるページを作ります
import React from 'react'; import MainContainer from './Components/MainContainer'; import './App.css'; const App =()=> { return( <div> <MainContainer /> </div> ) } export default App;
src以下にComponentsディレクトリを作成し、MainContainer.jsxを作成します
import React from 'react'; import axios from 'axios'; class MainContainer extends React.Component { state = { users: [] } // componentDidMount() は、コンポーネントがマウントされた(ツリーに挿入された)直後に呼び出されます // DOM ノードを必要とする初期化はここで行われるべきです componentDidMount() { axios .get('http://localhost:3001/users') .then((results) => { console.log(results) this.setState({users: results.data}) }) } render(){ return( <div></div> ) } } export default MainContainer;
axiosを使って、usersのページをgetします
このusersはindexにあたるので、usersコントローラーのindexからデータが送られてきます
送られてきたjsonは (results) に入り、 this.setState({users: results.data}) で state で管理できるようになります
railsを立ち上げてreactも立ち上げると、真っ白の画面が表示されますが、choromeのデベロッパーツールでconsoleを開くと3件のデータが表示されます
$ rails s -p 3001
(frontに移動して) $ npm start
このデータを実際に表示させます
Componentsディレクトリに、
UserContainer.jsx
ViewProduct.jsx
この2つのファイルを作ります
UserContainerは追加したデータをまとめるもの
ViewProductは1つ1つの単体データ
と言った感じです
import React from 'react'; import ViewProduct from './ViewProduct'; const UserContainer = props => { return( <div> { props.userData.map((data) => { return( <ViewProduct data={ data } key={ data.id } /> ) })} </div> ) } export default UserContainer;
親コンポーネントから渡ってきたpropsを、mapで1つ1つ渡して返してもらいます
import React from 'react'; const ViewProduct = props => { return( <div> <span>{ props.data.name }</span> </div> ) } export default ViewProduct;
親コンポーネントのMainContaineにはUserContainerを追加して、stateの値を渡すようにします
render(){ return( <div className="App"> <UserContainer userData={ this.state.users }/> </div> ) }
reduxを使うとまた別ですがstateだけの管理なのでスーパーバケツリレーですね
これでブラウザにはデータ(ユーザーの名前)が表示されていると思います
表示はできたので、ユーザーを追加するフォームを作ります
まずはmaterial-uiと言うもの導入します
これは簡単に言うと、bootstrapのようなもので、タグに名前をつけることで簡単に装飾ができるものです
(最近知ったので使ってみたかった)
公式はこちらです MATERIAL-UI
npmで導入します
$ npm i @material-ui/core
導入できたらフォーム部分を作ります
ComponentsディレクトリにFormContainer.jsxを作ります
import React from 'react'; import { FormGroup, FormControl, OutlinedInput, Button } from '@material-ui/core' class FormContainer extends React.Component { state = { user: '' } render(){ return( <div> <FormGroup> <FormControl> <OutlinedInput type="text" value={ this.state.user } style={{width: 300, margin: "0 auto"}} /> </FormControl> </FormGroup> <Button type="submit" onClick={ this.handleSubmit } variant="outlined" color="primary" > 追加 </Button> </div> ) } } export default FormContainer;
フォームの内容をMainContainer.jsxに加えます
render(){ return( <div className="App"> <FormContainer /> <UserContainer userData={ this.state.users }/> </div> ) }
フォームができました、が、入力をしても何も文字は表示されずボタンを押しても何も動きません
onChangeを使ってstateを変更して文字を表示させます
import React from 'react'; import { FormGroup, FormControl, OutlinedInput, Button } from '@material-ui/core' class FormContainer extends React.Component { state = { user: '' } onChangeText = (e) => { this.setState({user: e.target.value}) } render(){ return( <div> <FormGroup> <FormControl> <OutlinedInput type="text" value={ this.state.user } style={{width: 300, margin: "0 auto"}} onChange={ this.onChangeText } /> </FormControl> </FormGroup> <Button type="submit" onClick={ this.handleSubmit } variant="outlined" color="primary" > 追加 </Button> </div> ) } } export default FormContainer;
onChangeTextを作り、フォームの中で呼び出しstateを変化させ、valueに反映しています
それでは追加ボタンでDBに保存できるようにしていきます
まず react-addon-update と言うものを導入します
$ npm i react-addons-update
FormContanierの内容をUserContainerに反映させるので、2つの親コンポーネントでRailsとやりとりする記述を追加します
と言うことで、MainContainerに追加していきます
import update from 'react-addons-update'; <=追加 class MainContainer extends React.Component { // ---省略--- createUser = (name) => { axios .post('http://localhost:3001/users', { name: name }) .then((response) => { const newData = update(this.state.users, { $push: [response.data] }) this.setState({ users: newData }) }) } render(){ return( <div className="App"> <FormContainer createUser={ this.createUser } /> <UserContainer userData={ this.state.users }/> </div> ) }
ここで作成した createUser を FormContainer で呼び出します
class FormContainer extends React.Component { // ---省略--- handleSubmit = () => { this.props.createUser( this.state.user ) this.setState({ user: '' }) }
createUser に対する引数 name にはフォームで入力された文字が返ります
postメソッドなので、users_controller.rbのcreateに(name: params[:name])が返ります
(response) には作られたユーザーのデータが入るので、stateのusersにそのデータを挿入してからstateの更新を行っています
stateが更新されるのでviewはレンダリングされて即座に追加したユーザーが表示されます
コンソールを確認し、ユーザーのレコードが作成されていたら完成です
これで、ReactからRailsにデータを渡し、DBに保存することができました
1個1個動きを確認しながら記事を作成していたら5時間くらいかかってしまった...
削除や更新も覚えねば!またまとめていきます