はしばみあきら blog

プログラミングアウトプットするブログ。202010スタート

フロントReact、バックRailsでデータベースに保存する

最近Reactを色々やってみて、ふと「DBに保存するにはどうすれば良いのだろう?」と言う疑問が沸き調べたところReact単体でDBに保存するのはできないと言うことがわかりました。

ReactとはNode.jsとつなげた方がJS同士だからいいんだろうけど、慣れているRailsをバックにしてみます。

調べたことをアウトプットしながら書いていくのでコードだけ欲しい!みたいな人には冗長に感じるかもですがご愛敬。

RailsAPIとして使う

同じ初学者の人だとそもそも「RailsAPIで使うってどう言うこっちゃ?」ってなると思います。
自分もなりました。

Javascriptの入門本とかだとポピュラーなのはウェザーAPIだし
有名どころで言えばGoogleAPIとかYoutubeAPIとかですね。

そもそもAPIってなんぞ?と言うことです
以下のようなことらしいです

API」とは、「Application Programming Interface」の頭文字です。

英文字だけで意味を理解しようとすると、アプリケーション・プログラミング・インターフェイスで、大雑把に言うと「アプリケーションをプログラミングするためのインターフェース」という意味です。

インターフェイスとは、コンピュータ用語でいうと、「何か」と「何か」をつなぐものという意味を持ちます。例えば、USBも「パソコン」と「周辺機器」をつなぐものですので、インターフェイスの一つです。

つまり、APIとは、この「何か」と「何か」が「アプリケーション、ソフトウェア」と「プラグラム」をつなぐもの、という意味になります。

今さら聞けないIT用語:やたらと耳にするけど「API」って何?

つまりRailsでフロントとサーバーを繋ごうと言うことになるのかな

viewは完全にReactで作り、Railsにデータを渡してそこからDBにと言う感じですね

実際に作っていきます

RailsAPIとして作る

こちらが大変参考になりました
Ruby on Rails+ReactでCRUDを実装してみた

この記事を参考に進めます

まずはrails newです
お世話になっているRailsガイドに書いてあります
Rails による API 専用アプリケーション

API専用Railsアプリケーションの生成には次のコマンドを使います。

$ rails new my_api --api

--api をオプションでつけることでRailsAPI専用で作成できます

作られたアプリに移動したらモデルを作ります

$ 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

f:id:hashibamiakira:20201209010228p:plain

このデータを実際に表示させます

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だけの管理なのでスーパーバケツリレーですね

これでブラウザにはデータ(ユーザーの名前)が表示されていると思います

f:id:hashibamiakira:20201209014127p:plain

表示はできたので、ユーザーを追加するフォームを作ります

まずは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はレンダリングされて即座に追加したユーザーが表示されます

コンソールを確認し、ユーザーのレコードが作成されていたら完成です

f:id:hashibamiakira:20201209030412p:plain

これで、ReactからRailsにデータを渡し、DBに保存することができました

1個1個動きを確認しながら記事を作成していたら5時間くらいかかってしまった...

削除や更新も覚えねば!またまとめていきます