枠がまだ空いていたので、2日目の記事として投稿させてもらいます。まだ1ヶ月ほどしかreduxを触ってませんが、よろしくお願いします。
redux-formというformのデータをreduxで簡単に扱えるようにしてくれるライブラリがあります。redux-formは非常にドキュメントがしっかりしていて、サンプルコードもあって分かりやすかったです。このredux-formをredux-sagaと一緒に使おうとした時に少し手間取ってしまったので、サンプルコードを載せながらここに記そうと思います。
サンプルコード
redux-form-sagaという名前のライブラリがあるっぽいですが、それのサンプルコードじゃないです。
やりたいこと
- フォームの送信ボタンを押すとバックグラウンドでリクエストを送りたい
- サーバ側でエラーが返って来た場合はそれを画面に反映したい
- リクエストが成功した後に何かの処理をしたい
処理の流れ
- フォームの送信ボタンをクリック
- actionが発行されてredux-sagaが受け取る
- redux-sagaはサーバにリクエストを送信する
- 返ってきたレスポンスに応じてエラー表示や成功後の処理を行う
アクション
単純に受け取ったパラメータを流しています。
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の方を見てください。
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
に入るようにしています。
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経由でエラー情報を取得できるようになります。
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との相性が良さそうで、うまく使えてよかったです。