redux-formとredux-sagaを使ってみた

枠がまだ空いていたので、2日目の記事として投稿させてもらいます。まだ1ヶ月ほどしかreduxを触ってませんが、よろしくお願いします。

[redux-form](https://github.com/erikras/redux-form)というformのデータをreduxで簡単に扱えるようにしてくれるライブラリがあります。redux-formは非常にドキュメントがしっかりしていて、サンプルコードもあって分かりやすかったです。このredux-formを[redux-saga](https://github.com/yelouafi/redux-saga)と一緒に使おうとした時に少し手間取ってしまったので、サンプルコードを載せながらここに記そうと思います。

## サンプルコード
* [redux-form-saga-example](https://github.com/ABCanG/redux-form-saga-example)
* [デモページ](https://abcang.github.io/redux-form-saga-example/)

redux-form-sagaという名前のライブラリがあるっぽいですが、それのサンプルコードじゃないです。

## やりたいこと
* フォームの送信ボタンを押すとバックグラウンドでリクエストを送りたい
* サーバ側でエラーが返って来た場合はそれを画面に反映したい
* リクエストが成功した後に何かの処理をしたい

## 処理の流れ
1. フォームの送信ボタンをクリック
2. actionが発行されてredux-sagaが受け取る
3. redux-sagaはサーバにリクエストを送信する
4. 返ってきたレスポンスに応じてエラー表示や成功後の処理を行う

## アクション

単純に受け取ったパラメータを流しています。

```js:actions/index.js
import mirrorCreator from 'mirror-creator';

const actionTypes = mirrorCreator([
  'SUBMIT_FORM',
]);

export default actionTypes;

export function submitForm(params) {
  return {
    type: actionTypes.SUBMIT_FORM,
    payload: {params}
  };
}
```

## コンポーネント

redux-formではformの`onSubmit`に`handleSubmit`を渡すことでformの送信の制御を行っています。`handleSubmit`に関数を渡すと送信時に呼ばれるため、ここでは`submit`を指定しています。
`handleSubmit`に渡した関数は、第一引数にフォームのデータ、第二引数にdispatch関数が渡されて実行されるので、ここで`submitForm`アクションを発行してフォームのデータを流しています。

一部省略してあるので、全てのコードが見たい場合はgithubの方を見てください。

```js:containers/Form.jsx
import {submitForm} from '../actions';

function submit(value, dispatch) {
  dispatch(submitForm(value));
}

const Form = (props) => {
  const { handleSubmit, submitting, pristine, reset, error } = props;
  return (
    <form onSubmit={handleSubmit(submit)}>
      <Field name="username" type="text" component={renderField} label="Username" />
      <Field name="password" type="password" component={renderField} label="Password" />
      {error && <div><strong>{error}</strong></div>}
      {submitting && <div><strong>submitting</strong></div>}
      <div>
        <button type="submit" disabled={pristine || submitting}>Submit</button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>Clear Values</button>
      </div>
    </form>
  );
};

export default reduxForm({form: 'example'})(Form);
```

## サーバ通信のモック

今回のサンプルでは実際にサーバに送信しなくてもいいようにモックを用意しました。

`postLoginMock`は`axios.post`を使ったログインのようなものイメージしています。今回はパラメータのユーザ名やパスワードが空だったらログインに失敗して、どちらも空じゃなかったら成功するようにしています。
`login`はこの`postLoginMock`(`axios.post`)をラップして、成功時はレスポンスデータが`data`に入るように、失敗時はエラー内容が`error`に入るようにしています。

```js:util/index.js
function postLoginMock(url, params) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!params.username) {
        return reject({username: 'User does not exist', _error: 'Login failed!'});
      } else if (!params.password) {
        return reject({_error: 'Login failed!'});
      }
      return resolve({data: {message: 'success'}});
    }, Math.random() * 5000);
  });
}

export function login(params) {
  /*
  return axios.post('/login', params).then((res) => {
    return { data: res.data };
  }, (error) => {
    return { error };
  });
  */
  return postLoginMock('/login', params).then((res) => {
    return { data: res.data };
  }, (error) => {
    return { error };
  });
}
```

## saga

redux-sagaの部分が一番重要なポイントになります。
redux-formは`startSubmit`や`stopSubmit`などのredux-formを操作するためのアクションを提供しているので、これを利用しています。

まず、`take(actionTypes.SUBMIT_FORM)`の部分でアクションが発行されるのを待っています。
アクションが発行されると`startSubmit`を使用して送信を開始します。`startSubmit`の引数に渡している`'example'`は、コンポーネント作成時に`reduxForm`で指定した文字列と同じものを指定します。

次に`login`を呼び出し、サーバにデータを送信します。ログインに成功した場合は`stopSubmit`を呼んで、続けて成功時に行いたい処理を行います。ここではredux-formの`reset`を使ってフォームの内容を空にしています。
リクエスト成功後に処理を行いたい場合は別のアクションを呼び出す形にするとスッキリして良さそうです。

ログインに失敗した場合は、`stopSubmit`の第二引数にエラーオブジェクトを渡します。そうするとコンポーネント側からredux-form経由でエラー情報を取得できるようになります。

```js:sagas/index.js
import { fork, take, put, call } from 'redux-saga/effects';
import {startSubmit, stopSubmit, reset} from 'redux-form';

import actionTypes from '../actions';
import {login} from '../util';

function* handleSubmitForm() {
  for (;;) {
    const action = yield take(actionTypes.SUBMIT_FORM);
    const {params} = action.payload;
    yield put(startSubmit('example'));
    const { data, error } = yield call(login, params);
    if (data && !error) {
      // Action on success
      yield put(stopSubmit('example'));
      console.log(data.message);
      yield put(reset('example'));
    } else {
      // Action on failure
      yield put(stopSubmit('example', error));
    }
  }
}

export default function* rootSaga() {
  yield fork(handleSubmitForm);
}
```

## おわりに
redux-formのアクションを利用することでredux-sagaからフォームの送信を行うことができました。
redux-formはredux-sagaとの相性が良さそうで、うまく使えてよかったです。