Rails × React で Rails の scaffold ライクなアプリケーションを実装 - 一覧表示 -

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

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

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

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

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

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


##**メッセージ一覧を表示**
API用サーバー（Rails）, フロントエンド用サーバー（React含む）各々を立ち上げていない場合は、`rails s`と`yarn start`を叩いて立ち上げます。
（解説は省略します。[環境の構築](https://qiita.com/hibriiiiidge/items/8cc513807c9cfac8fd91)をご確認ください。）

###**Rails**
Rails側で一覧に表示するメッセージのデータを作成します。

##### **MessageのModelとControllerを作成**
今回のMessageは以下のようなイメージです。

|カラム名|型|null|
|:----|:----:|:---:|
|id| integer | false |
|title| string | false |
|content| text| true |

タイトル(title)は必須だけど内容(content)は必須ではないという感じです。

```ruby:./react_sample
rails g model Message title:string content:text
```

`db/migrate/2018xxxxxxxxxxx_create_messages.rb` というファイルが生成されているはずなので、そのファイルを開いて、`title`に`null: false`を設定します。

```ruby:db/migrate/2018xxxxxxxxxxx_create_messages.rb
class CreateMessages < ActiveRecord::Migration[5.1]
  def change
    create_table :messages do |t|
      t.string :title, null: false # => null: false を追加
      t.text :content

      t.timestamps
    end
  end
end

```

追加が終わったら `rails db:migrate` を叩きます。

```ruby:./react_sample
rails db:migrate
```

次に、Controllerを作成します。

```ruby:./react_sample
rails g controller Messages index
```

`messages_controller.rb` を編集します。

```ruby:./react_sample/app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def index
    messages = Message.all # =>追加
    render json: messages  # =>追加
  end
end
```
メッセージを全て取得して、それをjsonで返すという処理です。

##### **初期データを作成**

`db/seed.rb`に初期データを記述します。

```ruby:./react_sample/db/seed.rb
Message.create!([
  { title: 'Hello, World!', content: 'I like World!' },
  { title: 'Hello, Rails!', content: 'I like Rails!' },
  { title: 'Hello, React!', content: 'I like React!' }
])
```

記述したデータをセットするため以下のコマンドを叩きます。

```ruby:./react_sample
rails db:seed
```

`rails console`を叩いて、データがセットされたか確認しましょう。（解説は省力します。）

##### **ルーティングを設定**

`config/routes.rb`　を以下のように編集します。


```ruby:./react_sample/config/routes.rb
Rails.application.routes.draw do
  #get 'messages/index' #=> この行を削除
  resources :messages, only: :index, format: 'json' #=> この行を追加
end
```

##### **json形式のデータを確認**

`localhost:3000/messages.json` にアクセスします。
先ほど初期データで作成した値が確認できればOKです。

<img width="1426" alt="スクリーンショット 2018-02-06 20.52.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/cb001d19-a658-f9d2-7bcb-df0e943185b2.png">

###**React**
まずはメッセージ一覧画面Reactのコンポーネントの概要を確認したいと思います。
イメージは以下のような感じです。
<img width="491" alt="スクリーンショット 2018-02-07 9.44.54.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/e70619fc-82a5-c2bf-2d6e-0f96880f4da8.png">

|file/component|概要|
|:----|:---|
|index.html|このファイルで`index.js`を読み込んでいる。 Railsでいう`application.html`のようなイメージ　|
|index.js|このファイルで`App.js`を読み込んでいて、 それらを`index.html`に渡している。ブリッジ的な役割をしている。|
|App.js|このファイルで、これから実装するjsファイルやコンポーネントを読み込んで、ルーティングしたり表示のレイアウトを設定している。 |
|AppComponent|`App.js`の中にあり、全てのコンポーネントのベースとなるコンポーネント。　|
|AppHeaderComponent| `AppComponent`の中にある`Header`のコンポートネント |
|ListComponent|`AppComponent`の中にあるコンテンツ部分（一覧を表示する）のコンポーネント。この部分がフォームに変わったり、詳細表示に変わったりする。　　|
|AppFooterComponent|`AppComponent`の中にある`Footer`のコンポートネント |

それでは実装していきます。
##### **index.jsの修正**
[環境の構築](https://qiita.com/hibriiiiidge/items/8cc513807c9cfac8fd91)が完了した状態の`index.js`は以下のような内容になっているかと思いますので、そちらをまず修正します。

```jsx:./react_sample/frontend/src/index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {
  render () {
    return (
      <h1>Hello, React!</h1>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('app'));
```
を以下のように修正します。

```javascript:./react_sample/frontend/src/index.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('app'));
```
`App`クラスを削除して、`import App from './App';` という形で`App.js`を読み込むように修正しました。



##### **App.jsを作成**

次に、`src`の直下に`App.js`を作成します。

```javascript:./react_sample/frontend/src
touch App.js
```

そして、以下のように編集します。

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

//　アップコンポーネント
const App = () => (
  <div>
    <AppHeader />　
    <div>Messagelist</div>
    <AppFooter />
  </div>
)

//　ヘッダーコンポーネント　 #ここの実装が上のAppコンポーネントの<AppHeader />　にセットされる
const AppHeader = () => (
  <div>
    <h3>Message App</h3>
    <p>
      <span>[Home]</span>
      <span>[New Message]</span>
    </p>
  </div>
)

//　フッターコンポーネント　　　#ここの実装が上のAppコンポーネントの<AppFooter />にセットされる
const AppFooter = () => (
  <div>
    <p>
      Message App Footer
    </p>
  </div>
)

export default App
```

ここの段階で、一度 `localhost:4000`を確認します。
<img width="388" alt="スクリーンショット 2018-02-07 10.21.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/a80da274-3388-bed7-e63f-86ae84b15fc3.png">

上記のように表示されていれば成功です。
コンポーネントとしては以下のようなイメージです。
<img width="569" alt="スクリーンショット 2018-02-08 12.51.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/1ae93bc3-002b-5ab4-4372-257020613cd8.png">

##### **List.jsを作成**
次にメッセージ一覧を表示するコンポーネントである`List.js`を作成します。

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

まずはRails(API)からデータを取得するのではなくて、コンポーネント内でデータを定義してそれを表示してみます。

```jsx:./react_sample/frontend/src/List.js
import React, { Component } from 'react';
 
class List extends Component {
  constructor(props) {
    super(props)
    //　Listコンポーネントの state として messages を定義する
    this.state = {
      messages: [
        { id: 1, title: 'test_title1'},
        { id: 2, title: 'test_title2'}
      ]
    };
  }

  render(){
    //　Listコンポーネントの state を const messages にセットする const – 再代入不可能な変数
    const { messages } = this.state;
    return(
      <div>
        <ul>
          {messages.map((message) => {
            return <li key={message.id}> { message.title }</li>
          })}
        </ul>
      </div>
    );
  }
}

// Listコンポーネントをexportする。
export default List;
```
[React.jsでループするには。](https://qiita.com/konojunya/items/cb026a2aa3df1837d587)

そして、`App.js`を修正します。（変更のある箇所のみ表示しています。）

- `import List from './List';` を追加する
- Appコンポーネントの中でListコンポーネントを呼び出す。


```jsx:./react_sample/frontend/src/App.js
import React, { Component } from 'react';
import List from './List';

const App = () => (
  <div>
    <AppHeader />
    <List /> //<div>Messagelist</div>は削除する
    <AppFooter />
  </div>
)

~省略~
```

ここの段階で、再度`localhost:4000`を確認します。
<img width="538" alt="スクリーンショット 2018-02-08 20.17.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/e48cfcee-2ca0-7068-9891-b7684c46981b.png">

Listコンポーネントで定義したmessagesが表示されていれば成功です。(上記イメージの左側)

##### **Rails（API）からデータを取得する**

現状のままReactからRailsにアクセスすると`No 'Access-Control-Allow-Origin' header is present on the requested resource.`のようなクロスドメインに起因するエラーが出てアクセス出来ません。

そこで次の設定を行います。（詳細は省略します。）
１. gem `rack-cors` をインストールする。
２. `application.rb`に以下を追加する。
 
```ruby:config/application.rb
class Application
 ~省略~
 config.middleware.insert_before 0, Rack::Cors do
  allow do
   origins 'localhost:4000'
   resource '*', headers: :any, methods: [:get, :post, :put, :delete]
  end
 end
end
```
３. Railsを再起動させる。

- [Rails APIでクロスドメインとpre-flight対応](https://qiita.com/shimpeiws/items/0cd53ac9da3a7fd645b9)
- [Rails5 apiとAngularで開発時のCORS（クロスドメイン）対応](http://ktmry.hatenablog.com/entry/2017/10/06/150000)

そちらを設定が終わったら、`List.js`を以下のように修正します。

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

const REQUEST_URL = 'http://localhost:3000/messages.json'
 
class List extends Component {
  constructor(props) {
    super(props)
    this.state = {
      messages: []
    };
  }
  // renderが呼ばれる前にDataをfetchする処理を発火する。
  componentWillMount() {
    this.fetchData()
  }
  
  fetchData() {
    // fetchで非同期処理を実行する。
    fetch(REQUEST_URL)
      .then((response) => {
      // 4xx系, 5xx系エラーのときには response.ok = false になる
       if (!response.ok) {
         throw Error(response.statusText);
       }
       return response;
      })
      // レスポンスボディをJSONとしてパースする
      .then((response) => response.json())
      // responseDataをmessagesにsetする
      // setStateが行われる度にrenderが呼ばれ、表示が変わる
      .then((responseData) => {
        this.setState({
          messages: responseData
        })
      })
      // エラーをcatchする
      .catch((err) => {
        console.error(err);
      });
    })
  }
 
  render(){
    const { messages } = this.state;
    return(
      <div>
        <ul>
          { messages.map((message) => {
            return <li key={message.id}>{message.title}</li>
          })}
        </ul>
      </div>
    );
  }
}
export default List;
```

- [Fetch API が 4xx エラーを reject してくれない](http://blog.mudatobunka.org/entry/2016/04/26/092518)

それでは`localhost:4000`を確認します。
<img width="309" alt="スクリーンショット 2018-02-08 20.06.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/114886/58ea6993-14c2-6fd3-f599-f662ed3ec203.png">

Railsでセットした初期データの`title`が表示されていれば成功です。

- [React component ライフサイクル図](https://qiita.com/kawachi/items/092bfc281f88e3a6e456)

次は、新規メッセージを実装してみたいと思います。[こちら](https://qiita.com/hibriiiiidge/items/5d65d721c605ed597fcb)