React + ReduxでREST APIを叩いてリスト表示する方法

## 1. はじめに
React + Reduxで，ajaxでデータを取得してリストを表示しようとしたら，なかなかにハマったのでメモががてら共有したいと思います。（ひととおりドキュメントを読んでから実装したにも関わらず，ハマった。。。）

### 1-1. アウトライン
本記事のアウトラインは以下です。

1. はじめに  
    1-1. アウトライン  
    1-2. 作りたい機能概要
2. Middlewareの導入
3. Actionの実装
4. Reducerの実装
5. Componentsの実装  
     5-1. Container Component  
     5-2. Presentational Component  
6. まとめ

### 1-2. 作りたい機能概要
作るのは，ユーザの投稿（Post）の一覧を表示する機能です。サーバー側はGETリクエストすると

```js
{
    _id: 'aaaaaaaa',
    body: 'bbbbbbbb',
    created_at: 'cccccccc',
    updated_at: 'dddddddd'
}
```

というjsonを返す仕様になっています。このAPIにアクセスしてデータを取得し，画面に表示するという機能です。
表示は次のような感じで，`body`のテキストを表示します。

![スクリーンショット 2018-01-08 11.16.22.png](https://qiita-image-store.s3.amazonaws.com/0/56920/ba8c1f90-fc62-98f8-d16e-005cc7bea735.png)

## 2. Middlewareの導入
まず，Reduxで非同期通信をするためには， [redux-thunk](https://github.com/gaearon/redux-thunk)というミドルウェアを使用する必要があります。Reduxアプリケーションにおいて，Action CreatorはシンプルなActionオブジェクトを返すことしかできない（非同期APIの呼び出しとかしてはいけない）のですが，Thunkを使うとそれが可能になります。Action Creatorで関数を返すと，Thunkがそれを受け取ってうまいこと処理してくれるようです。
redux-thunkはnpmでインストールできます。

```console
$ npm i --save redux-thunk
```

ついでに，redux-loggerも導入しておくと，状態の変更毎にログを出してくれるのでデバック時に役立ちます。これもnpmでインストールできます。

ミドルウェアの導入は， `index.jsx`で`createStore()`にapplyMiddleware()を設定することでできます。

```jsx:index.jsx
import React from 'react'
import { render } from 'react-dom'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import App from '~/components/App'
import rootReducer from '~/reducers/rootReducer'
import { getPosts } from '~/actions/postAction'

const store = createStore(
  rootReducer,
  applyMiddleware(thunk, logger)
)

store.dispatch(getPosts())

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
```

なお，今回はこのファイルの読み込み時に，後でActionで定義するgetPosts()をdispatchしています。（今はとりあえず表示できればいいので）

## 3. Actionの実装
次にActionを実装します。Reduxの初学者が非同期通信を使用するアプリを実装する際，

「**どこでリクエストを投げるのが適切なのか**」

という問題が立ちはだかると思います。
答えは，**「ミドルウェアを導入して，Action Creatorで行なう」**です。最初，Reducerで処理を記述したほうが自然な気がしてしまいます（僕だけ？）が，それは気のせいです。

Actionで必要なデータを準備してdispatchし（ミドルウェアを利用），Reducerでactionが持つ情報をもとに，stateを更新するというのが正しい方法のようです。Actionは基本的にシンプルなオブジェクトを返さなければいけないため，本来であれば非同期APIをコールするというような複雑な処理はできません。そこをミドルウェアを使用するとできるようになるようです。

公式ドキュメントに，Action Creatorを拡張する様々な方法が書いてあります。その中でも，一番良く使われるのがThunkミドルウェアを使用する方法のようなので，今回はThunkを使用します。

コードは以下のようになっています。

```jsx:actions/Action.jsx
import axios from 'axios'
export const GET_POSTS_REQUEST = 'GET_POSTS_REQUEST'
const getPostsRequest = () => {
  return {
    type: GET_POSTS_REQUEST
  }
}

export const GET_POSTS_SUCCESS = 'GET_POSTS_SUCCESS'
const getPostsSuccess = (json) => {  
  return {
    type: GET_POSTS_SUCCESS,
    posts: json,
    receivedAt: Date.now()
  }
}

export const GET_POSTS_FAILURE = 'GET_POSTS_FAILURE'
const getPostsFailure = (error) => {
  type: GET_POSTS_FAILURE,
  error
}

export const getPosts = () => {
  return (dispatch) => {
    dispatch(getPostsRequest())
    return axios.get(`http://localhost:3000/api/v1/posts`)
      .then(res =>
        dispatch(getPostsSuccess(res.data))
      ).catch(err => 
        dispatch(getPostsFailure(err))
      )
  }
}
```

GETするときに必要になるActionは全部で3つです。

- `GET_POSTS_REQUEST`：リクエスト開始時にdispatchされ，isFetchingをtrueにする
- `GET_POSTS_SUCCESS`：リクエスト成功時にdispatchされ，isFetchingをfalseにしてデータをstateにセットする
- `GET_POSTS_FAILURE`：リクエスト失敗時にdispatchされ，isFetchingをfalseにしてエラーオブジェクトをstateにセットする

`getPosts()`をdispatchすることでリクエストを送信してデータをストアすることができます。

また，公式ドキュメントでは`isomorphic-fetch`を使用していますが，今回は`axios`ライブラリを使用しています。

## 4. Reducerの実装
Reducerは以下のようになります。

```jsx:postReducer.jsx
import {
  GET_POSTS_REQUEST, GET_POSTS_SUCCESS, GET_POSTS_FAILURE
} from '~/actions/postAction'

const initalState = {
  isFetching: false,
  items: []
}

const posts = (state = [initalState], action) => {    
  switch (action.type) {
    case GET_POSTS_REQUEST:
      return [
        ...state,
        {
          isFetching: true,
          items: []
        }
      ]
    case GET_POSTS_SUCCESS:
      return [
        ...state,
        {
          isFetching: false,
          items: action.posts,
          lastUpdated: action.receivedAt
        }
      ]
    case GET_POSTS_FAILURE:
      return [
        ...state,
        {
          isFetching: false,
          error: action.error
        }
      ]
    default:
      return state
  }
}

export default posts
```

`GET_POSTS_SUCCESS`アクションがdispatchされると，actionからpostsを取り出してitemsに格納しています。このデータをコンポーネントで使用することになります。尚，stateは基本的に上書きせずに，配列の後ろに追加していくことに注意してください。

また上のReducerは`combineReducer()`で読み込んでいます。

```jsx:rootReducer.jsx
import { combineReducers } from 'redux'
import posts from './postReducer'
const rootReducer = combineReducers({
  posts
})

export default rootReducer
```

## 5. Componentの実装
ここが一番ハマりました。今思えば，Reduxというよりjsvascriptの基本的な事項の理解が足りてなかった。。。

さて，Reduxアプリケーションでは，コンポーネントが2種類必要になります。この2つはざっくり以下のような特徴があります。

- Container Component  
  => Reduxのstoreに格納されているデータをPresentational Componentに紐付けるためのコンポーネント。
- Presentational Component  
  => Reactアプリの普通のコンポーネント。Container Componentで紐付けられたデータを使用して実際に画面を描画する。

それぞれについて見ていきます。

### 5-1. Container Component
Container Componentでは，Presentational Componentで必要になるデータをstateから取り出し，

```js
connect('渡したいオブジェクト')('コンポーネント')
```

としてComponentへ紐付けます。

リストを取得するContainer Componentは以下のようになります。

```jsx:containers/GetPostListContainer.jsx
import {connect} from 'react-redux'
import PostList from '~/components/post/PostList'

const mapStateToProps = (state) => {    
  const length = state.posts.length
  const currentState = state.posts[length - 1]  // 一番新しいstateを取り出す
  return { posts: currentState.items }  // 描画するのに必要なのはとりあえずitemsだけなのでitemsだけ返す
}

const GetPostList = connect(
  mapStateToProps
)(PostList)

export default GetPostList
```

### 5-2. Presentational Component

Presentational Componentは以下のようになります。

```jsx:components/PostList.jsx
import React from 'react'
import PropTypes from 'prop-types'
import Post from './Post'

const PostList = ({ posts }) => (
  <ul>
    { posts.map((post, index) => 
        <Post key={index} {...post}/>
      )
    }
  </ul>
)

PostList.propTypes = {
  posts: PropTypes.arrayOf(
    PropTypes.shape({
      _id: PropTypes.object.isRequired,
      body: PropTypes.string.isRequired,
      created_at: PropTypes.string.isRequired,
      updated_at: PropTypes.string.isRequired
    }).isRequired
  ).isRequired
}

export default PostList
```

`prop-types`を使って，`posts`の型を定義しています。Container Componentで渡したデータの形を定義しておきます。
渡したデータが間違っていた場合，エラーを出してくれます。

ここで，上手くPostコンポーネントにデータを渡せないという現象に頭を悩まされました。うまくいったコードは以下です。

```jsx:components/Post.jsx
import React from 'react'
import PropTypes from 'prop-types'

const Post = ({ _id, body }) => (
  <li>
    { body }
  </li>
)

Post.propTypes = {
  post: PropTypes.shape({
    _id: PropTypes.object.isRequired,
    body: PropTypes.string.isRequired,
    created_at: PropTypes.string.isRequired,
    updated_at: PropTypes.string.isRequired
  })
}

export default Post
```

これを，ずっと

```jsx
const Post = ({ post }) => (
  <li>
    { post.body }
  </li>
)
```
としていて上手く渡せていませんでした。これ，よく考えるとpostの要素を直接引数に設定して，受け取らないとだめですよね。
なぜなら，`PostList.jsx`でPostコンポーネントにデータを渡す際に，

```html
<Post key={index} {...post}/>
```

として， `post`オブジェクトを展開しています。よって，渡されるデータは展開されたあとのデータになるので，要素名で受け取らないと受け取れないということです。ヤラレタ。。。というか，これRedux，Reactというより，ES6の理解不足ですね。

最後に，App.jsxは以下のようにして，Container Componentをセットします。

```jsx:components/App.jsx
import React, {Component} from 'react'
import GetPostList from '~/containers/GetPostListContainer'

const App = () => (  
  <div>
    <GetPostList />
  </div>
)

export default App
```

### 補足
直接Reduxとは関係ないですが，ちょっとハマったのでメモしておきます。
map関数のコールバックの中身の括弧の違いで，子コンポーネントがreturnされるかどうか違うようです。
おそらく当たり前のことなんだろうけど，最初わからなくてなんで描画されないんだ〜！ってなりました。試していたら以下のような法則があるっぽい。

```jsx
posts.map(post => {
  <Post />
})
```

↑ Postコンポーネントはreturnされないため表示されない

```jsx
posts.map(post => 
  <Post />
)

posts.map(post => (
  <Post />
))
```
↑ Postコンポーネントがreturnされ，リストが表示される


## 6. まとめ
ES6のスプレッド演算子とか，コールバック関数のreturnの仕様とかではまりました。javascriptの基本なのに理解できておらず，お恥ずかしい。。。
でも，悩む過程でReact + Reduxの理解が深まったので良しとしましょう。誰かのお役に立てれば幸いです。

## 参考
[Redux公式ドキュメント](https://redux.js.org/)
[Reduxサンプル](https://github.com/reactjs/redux/blob/master/examples/todos/)
[reduxを試してみた(5日目) - ajaxを使ってUIを構築する(reduxにおける非同期の制御)](https://qiita.com/kompiro/items/d1ffcfcba7cc34d364f0)
[Fetch vs. Axios.js for making http requests](https://medium.com/@thejasonfile/fetch-vs-axios-js-for-making-http-requests-2b261cdd3af5)
[Reduxの基本 ~ 公式ドキュメント Basics~](https://qiita.com/kazmaw/items/9fb069c551d33a7c1c84)
[ReduxのAdvancedな使い方 ~ 公式ドキュメント Advanced~](https://qiita.com/kazmaw/items/d1a1decafc98c0dafce4)