Reduxを学んだ(2) ~Middleware編(e.g. ReduxThunk)~

- 第1部は、[Reduxを学んだ(1) ~StateとActionとReducer~](https://qiita.com/kudojp/items/fc4a504d00bb76664ca4)


## Reduxミドルウェアとは？

- store.dispatchが呼び出れてから実際にstateを更新されるまでの間に、処理を挟み込むためのもの

## Reduxミドルウェアの例

### Loggerミドルウェア

- 以下みたいな感じで実装できるだろう。

```js
    const logger = (state) => (next) => (action) => {
      console.group(action.type)
        console.log('The action: ', action)
        const result = next(action)         // 次の処理
        console.log('The new state :', store.getState())
      console.groupEnd()
      return result
    }
```



### ReduxThunkミドルウェア

ライブラリとして提供される。中身の骨格は以下のような感じである。

```js

    const thunk = (store) => (next) => (action) => {
      if (typeof action === 'function') {
        // これを満たすactionは"handleDeleteTodo(todo)の返り値のcallback関数"とか
        return action(store.dispatch)
      }
      return next(action)
    }

```




要は、dispatchに渡されるactionが、(オブジェクトではなく)callback関数の時に発動される。このcallback関数は、`dispatchを引数に取り、行いたい処理を行うcallback関数`であることを前提としている。

```js

    // Action Creator
    function handleInitialData() {
      return (dispatch) => {          // callback関数を返している
        return Promise.all([
          API.fetchTodos(),
          API.fetchGoals()
        ]).then(([todos,  goals]) => {
          dispatch(receiveDataAction(todos, goals))     // 新たにactionを初期化してdispatchしている
        })
      }
    }


    class App extends React.Component {
      componentDidMount () {
        const { dispatch } = this.props
        dispatch(handleInitialData())        // callback関数が渡されている -> Redux Thunksミドルウェアで処理される
      }

     render(){...}
    }
```

Redux Thunkミドルウェアは主に非同期処理を行いたい時に使用できる。これを使用することで、APIリクエストを行うdata fetchingのコードをComponentの中ではなく、ActionCreatorの中に含めることができる。

ミドルウェアの中で新たにactionがdispatchされるというところが面白い。


## ミドルウェアの使用方法

```js
    const store = Redux.createStore(
      Redux.combineReducers({todos, goals}),
      Redux.applyMiddleware(ReduxThunk.default, checker, logger)
    )
```
















# おまけ１(Optismistic UI)

itemの削除時はUIで一旦消してしまう。API通信でDBの更新に失敗した場合は、そのitemを復活させる。

```js
    class Todos extends React.Component {
      addItem = (e) => {
        e.preventDefault()
        const name = this.input.value

        // 新しくTodoが作られる時、そのidはサーバーサイドで作られるので、
        // UIをoptimistic updateすることはしない
        return API.saveTodo(this.input.value)
          .then((todo) => {
            this.props.store.dispatch(addTodoAction(todo))
            this.input.value = ''
          })
          .catch(() => {
            alert('An error occurred when adding Todo. Try again!')
          })
      }

      removeItem = (todo) => {
        this.props.store.dispatch(removeTodoAction(todo.id))
        return API.deleteTodo(todo.id)
          .catch(() => {
            // Optimistic UI
            // これだとstore.todosの末尾に追加されるのでtodosの並びが変わるが、、、
            this.props.store.dispatch(addTodoAction(todo))
            alert('An error occurred when removing Todo. Try again!')
          })
      }

      render() {...}
    }
```


# おまけ２(inputタグのSingle Source of Truth)


inputタグについて、「ユーザの入力内容をstateに保持し、随時更新すべきである」「inputの中のvalueは随時このstateを反映すべきである」(single source of truthを実現するため)というのが面白かったのでメモっておく。

```js
    class Todos extends React.Component {
      addItem = (e) => {
        e.preventDefault()
        this.props.dispatch(handleAddTodo(
          this.input.value,
          () => this.input.value = ''
        ))
      }

      render() {
        return (
          <div>
            <h1>Todos</h1>
            <input
              type='text'
              placeholder='Add Todo'
              // inputのvalueが更新されるたびに、input DOM(要はrefを含んでいるDOM)を引数にこのcallback関数が実行される
              // つまり、ユーザが入力した内容は随時state(input)に保持される!
              ref={(input) => this.input = input}
            />
            <button onClick={this.addItem}>Add Todo</button>
          </div>
        )
      }
    }
```