Help us understand the problem. What is going on with this article?

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

More than 3 years have passed since last update.

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

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

サンプルコード

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

やりたいこと

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

処理の流れ

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

アクション

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

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

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

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);

サーバ通信のモック

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

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

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はstartSubmitstopSubmitなどのredux-formを操作するためのアクションを提供しているので、これを利用しています。

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

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

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

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との相性が良さそうで、うまく使えてよかったです。

abcang
JavaScriptとかRubyとかがすきです
https://abcang.net
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away