Rails × React で Rails の scaffold ライクなアプリケーションを実装 - 新規登録 -

Rails×ReactでRailsのscaffoldで生成されるような簡単なcrudができるアプリケーションを実装したので、備忘録として残しておこうと思います。

###**アプリケーションの概要**
タイトルと本文のあるメッセージをCRUD＋絞り込み検索するアプリケーション

### **やったこと**
(1) [環境の構築](https://qiita.com/hibriiiiidge/items/8cc513807c9cfac8fd91)
(2) [メッセージ一覧を表示する。](https://qiita.com/hibriiiiidge/items/50c200445a0803ac11b7)
(3) 新規メッセージを作成する。（本エントリ）
(4) [メッセージ詳細の表示、編集、更新をする。](https://qiita.com/hibriiiiidge/items/3dbb022b90b4c21773d2)
(5) [メッセージを削除する、メッセージを検索する。](https://qiita.com/hibriiiiidge/items/e99195771ec145ab68e8)

###**やってないこと**
- Redux、MobXのステート管理のためのフレームワークの導入
- テストの実装
- CSSの実装
- セキュリティやデータの整合性を確認する実装
- 用語や実装の解説

用語や実装の解説については、参考にさせていただいたサイトのリンクを各実装毎に貼っておりますので、そちらをご確認下さい。
参考サイトの作成者の皆様ありがとうございます。


【ご一読いただくにあたって】
・間違いがありましたらコメントにて教えて下さい。（まだまだ勉強中です。）

##**新規メッセージを登録**
<img width="274" alt="スクリーンショット 2018-02-09 9.50.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/5f7cb72f-32ce-affa-bf31-e775455e0b94.png">

新規登録のフローは以下のような流れをイメージしています。
1. [New Message]　をクリックすると新規登録画面が表示される。
2. 新規登録画面では、メッセージのタイトルと詳細を入力するフォームが表示されている。
3. [submit]をクリックするとメッセージが登録されて、一覧が表示される。
4. 一覧画面では 3.で登録したメッセージのタイトルが表示されている。

|画面|URL|
|:----|:----|
|一覧画面|`http://localhost:4000/`|
|新規登録画面| `http://localhost:4000/new` |

###**React**
#### **[New Message]をクリックと`New.js`のNewコンポーネントが表示されるようにする**
##### **New.jsを作成**

```ruby:./react_sample/frontend/src
touch New.js
```

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

class New extends Component {
  render() {    
    return (
      <div>
        <p>New Message</p>
        <p>New Message Form</p>
      </div>
    );
  }
}

export default New;
```

##### **ルーティングを設定**
ReactはSPAなので、URLを変えずに新規登録画面（Newコンポーネント）を表示することも可能だと思うのですが、今回は新規登録画面（Newコンポーネント）のURLを`http://localhost:4000/new`というように設定したいと思います。

###### **react-routerのインストール**

```ruby:./react_sample/frontend
yarn add react-router react-router-dom
```

- [React Router v4(next) が alphaからbetaになってぼちぼち変わってたので追従する](https://qiita.com/inuscript/items/52d75db151a1a2e4257b)

###### **App.jsにルーティングを設定**

```javascript:./react_sample/frontend/App.js
import React, { Component } from 'react';
// react-router-dom をインポートする
import { BrowserRouter as Router, Link, Switch, Route } from 'react-router-dom';
import List from './List';
import New from './New';

const App = () => (
  // <Router>でAppコンポーネントをラップする
  <Router>
    <div>
      <AppHeader />
      // 現在のURLが path と一致した場合に component= でセットしているコンポーネントをレンダリングする
      <Switch>
         // exact によって、path　の記述と一致するURLの場合にのみレンダリングする
         // exact がないと /new/example という path があったとしても /new の方がレンダリングされてしまう
         <Route exact path='/' component={ List }/>
         <Route exact path='/new' component={ New } />
       </Switch>
      <AppFooter />
    </div>
  </Router>
)

const AppHeader = () => (
  <div>
    <h3>Message App</h3>
    <p>
      // Link to で a が生成される
      <Link to="/">[Home]</Link>
      <Link to="/new">[New Message]</Link>
    </p>
  </div>
)

const AppFooter = () => (
  <div>
    <p>
      Message App Footer
    </p>
  </div>
)

export default App
```

[Home]をクリックするとURLが　'/'　となりListコンポーネントがレンダリングされて、
[New Message]をクリックするとURLが　'/new'　となり、Newコンポーネントがレンダリングされます。

コンポーネントは以下のようなイメージです。

<img width="423" alt="スクリーンショット 2018-02-14 10.09.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/0cfc2e39-ff17-5fac-4f6f-9c265ca481ad.png">


- [React Router v4 の基本的な考え方と使い方](http://numb86-tech.hatenablog.com/entry/2017/05/06/125333)

それでは `localhost:4000`を確認してみます。
![AwesomeScreenshot-2018-02-10T11-28-01-174Z.gif](https://qiita-image-store.s3.amazonaws.com/0/114886/7a6b6948-28e1-b494-f8a1-03be181d3c26.gif)

#### **新規登録画面でメッセージのタイトルと詳細を入力するフォームが表示されるようにする**
##### **入力フォームを実装**

次にNew.jsにメッセージの入力フォームを実装していきます。

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

class New extends Component {
  render() {    
    return (
      <div>
        <p>New Message</p>
        <p>New Message Form</p>
      </div>
    );
  }
}

export default New;
```
を以下のように修正します。

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

class New extends Component {
  //　コンポーネントの初期化　インスタンスを作成したタイミングで実行される
  constructor(props){
    // super(props)を呼び出すことで、 this　を使用できる
    super(props);
    // Message の title と content を this.state にセットする
    this.state = {
      title: '',
      content: ''
    };
  }
  
  render() {
   // const にセットすることで、　value={ this.state.title　} と書かなくて済む
   const { title, content } = this.state;
   return (
     // return では1つのコンポーネントしか返すことが出来ない
     // return (
     //       <div>...</div>
     //       <div>...</div>
     // );
     // ↑はエラーとなる
     <div>
       <p>New Message</p>
       <div>
         <form>
           <label>title:</label>
           <input type="text" name="title" value={ title } />
           <label>content:</label>
           <textarea name="content" value={ content } />
           <input type="submit" value="Submit" />
         </form>
       </div>
     </div>
   );
 }
}

export default New;
```
`constructor` や `super(props)` の詳細は、[公式リファレンス](https://reactjs.org/docs/react-component.html#constructor)を確認するといいかもです。

これで入力フォームが表示されたと思いますので、`localhost:4000` を確認してみましょう。（イメージは省略）

しかし、今のままだと　title　に文字を入力しても、文字がinputタグに反映されないと思います。
文字を反映させるには　inputタグ や textareaタグ に onChangeイベント を実装し、 setState を更新しなければなりません。

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

class New extends Component {
  constructor(props){
    super(props);
    this.state = {
      title: '',
      content: ''
    };
    // this を bindする
    this.handleInputValue = this.handleInputValue.bind(this);
  }
  // handleInputValue を実装
  handleInputValue(event){
    const field = event.target.name;
    this.state[field] = event.target.value;
    // setState することでレンダリングされる
    this.setState({
      [field]: this.state[field]
    });
  }

  render() {    
   const { title, content } = this.state;
   return (
     <div>
       <p>New Message</p>
       <div>
         <form>
           <label>title:</label>
           // onChange で handleInputValue　を発火させる
           <input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
           <label>content:</label>
           <textarea name="content" value={ content } onChange={ this.handleInputValue } />
           <input type="submit" value="Submit" />
         </form>
       </div>
     </div>
   );
 }
}

export default New;
```
###### **アロー関数**
handleInputValue　のような関数が増えていく度に constructor でbindするのは手間です。
アロー関数を使うことでその手間を削減することができるので、アロー関数を使えるように設定したいと思います。

`babel-preset-stage-0` をインストールします。

```ruby:./react_sample/forntend
yarn add babel-preset-stage-0
```
`.babelrc` を以下のように編集します。

```ruby:./react_sample/forntend/.babelrc
{
  "presets": [
    "es2015", "react", "stage-0" # "stage-0"を追加
  ]
}
```
これでアロー関数を使えるようになったので、 `New.js` を以下の様に修正します。

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

class New extends Component {
  constructor(props){
    super(props);
    this.state = {
      title: '',
      content: ''
    };
    // this.handleInputValue = this.handleInputValue.bind(this);
  }

  //handleInputValue(event){
  handleInputValue = (event) => {
    const field = event.target.name;
    this.state[field] = event.target.value;
    this.setState({
      [field]: this.state[field]
    });
  }

  render() {    
   const { title, content } = this.state;
   return (
     <div>
       <p>New Message</p>
       <div>
         <form>
           <label>title:</label>
           <input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
           <label>content:</label>
           <textarea name="content" value={ content } onChange={ this.handleInputValue } />
           <input type="submit" value="Submit" />
         </form>
       </div>
     </div>
   );
 }
}

export default New;
```

#### **[submit]をクリックするとメッセージが登録されて、一覧が表示される**
それでは次に [submit]かクリックされてメッセージが登録されるまでを実装します。

##### **handleSubmitを実装**

submitがクリックされた時のイベントを実装します。

```javascript:./react_sample/frontend/src/New.js
import React, { Component } from 'react';

// Request を送るURLを指定（RailsのAPIサーバ）
const REQUEST_URL = 'http://localhost:3000/messages.json'

class New extends Component {
  constructor(props){
    super(props);
    this.state = {
      title: '',
      content: ''
    };
  }

  handleInputValue = (event) => {
    const field = event.target.name;
    this.state[field] = event.target.value;
    this.setState({
      [field]: this.state[field]
    });
  }
  
  // submitがクリックされた時の処理
  handleSubmit = (event) => {
    // デフォルトの遷移を抑制する
    event.preventDefault();
    // $.ajax()に代替する fetch 
    // 結果がPromiseで返される
    fetch(REQUEST_URL, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
      },
      //  JSON.stringifyで　JSON 文字列に変換する
      body: JSON.stringify({
        message: {
          title: this.state.title,
          content: this.state.content
        }
      })
    })
    .then((response) => {
      if (!response.ok) {
        throw Error(response.statusText);
      }
      // 一覧コンポーネントを表示する
      this.props.history.push('/');
    })
    .catch((err) => {
      console.error(err);
    });
  }

  render() {    
   const { title, content } = this.state;
   return (
     <div>
       <p>New Message</p>
       <div>
         <form onSubmit={ this.handleSubmit }>
           <label>title:</label>
           <input type="text" name="title" value={ title } onChange={ this.handleInputValue } />
           <label>content:</label>
           <textarea name="content" value={ content } onChange={ this.handleInputValue } />
           <input type="submit" value="Submit" />
         </form>
       </div>
     </div>
   );
 }
}

export default New;
```

- [Fetch API 解説、または Web において "Fetch する" とは何か？](http://jxck.hatenablog.com/entry/whatwg-fetch)
- [jQuery.ajax()の代替としてFetch APIをざっくり使ってみる](http://tacamy.hatenablog.com/entry/2016/10/16/182658)
- [React Routerのhistoryはどこから来るのか](http://www.techscore.com/blog/2017/12/01/where_does_react-router-history_come_from/)
- [Response](https://developer.mozilla.org/ja/docs/Web/API/Response)

まだRails側を実装していないので、登録は出来ませんが、`localhost:4000`で挙動を確認します。
![AwesomeScreenshot-2018-02-10T13-04-11-559Z.gif](https://qiita-image-store.s3.amazonaws.com/0/114886/57050981-e1c7-9af1-1ca2-8f7a83f33847.gif)

（スクリーンショットの動画機能の精度が悪いせいだと思うのですが、レンダリングが遅く見えますが、実際にはこんなに遅くないと思います。実際遅くないです。）

### **Rails**
##### **メッセージの登録(APIサーバ)**
今回の記事のメインはReact側なので、Rails側の解説は省略したいと思います。

```Ruby:./react_sample/app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
    messages = Message.all
    render json: messages
  end

  def create
    message = Message.new(message_params)
    message.save!
  end

  private

  def message_params
    params.require(:message).permit(:title, :content)
  end
end
```

```Ruby:./react_sample/config/routes.rb
Rails.application.routes.draw do
  resources :messages, only: [:index, :create], format: 'json'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
```

Rails側の実装を完了したら、`localhost:4000`を確認してみます。
以下のような挙動になっていれば成功です。

![AwesomeScreenshot-2018-02-10T13-28-04-832Z.gif](https://qiita-image-store.s3.amazonaws.com/0/114886/e829d9f9-3a2e-7a56-a4b1-953c35ac9f91.gif)

次は、メッセージの詳細/編集/更新を実装してみたいと思います。[こちら](https://qiita.com/hibriiiiidge/items/3dbb022b90b4c21773d2)