Redux FormのWizard FormでSubmit Validationを使う方法

今回はRedux FormのWizard FormでSubmit Validationを使う方法です。Material UIも使っていますが、今回はあまり話の本筋ではありません。

現時点においてReduxでFormを扱うにはRedux Formを選択するケースがメジャーなようです。 http://redux-form.com/6.5.0/

なお、Exampleで紹介されているドキュメントと実際のコードが違っている場合がある(ドキュメントの方だと引数が書いてなかったのとかあった)ようなので、最終的には https://github.com/erikras/redux-form/tree/master/examples の方を確認しておいたほうが確実です。

複数画面にまたがってフォーム入力がある場合

例えば入力項目が多い場合に画面遷移しつつフォームを入力する場合には Wizard Form ( http://redux-form.com/6.5.0/examples/wizard/ )が参考になります。

注意点としては以下の4点になります。

  • Connect each page with reduxForm() to the same form name.
  • Specify the destroyOnUnmount: false flag to preserve form data across form component unmounts.
  • You may specify sync validation for the entire form
  • Use onSubmit to transition forward to the next page; this forces validation to run.

また、Submit後には自前で props.destroy() を呼んで、入力値をクリアする必要があります。

画面遷移をコントロールするベースとなるのが WizardForm.js になります。

https://github.com/erikras/redux-form/blob/master/examples/wizard/src/WizardForm.js

以下抜粋です。

class WizardForm extends Component {
  nextPage() {
    this.setState({ page: this.state.page + 1 })
  }

  previousPage() {
    this.setState({ page: this.state.page - 1 })
  }

  render() {
    const { onSubmit } = this.props
    const { page } = this.state
    return (<div>
        {page === 1 && <WizardFormFirstPage onSubmit={this.nextPage}/>}
        {page === 2 && <WizardFormSecondPage previousPage={this.previousPage} onSubmit={this.nextPage}/>}
        {page === 3 && <WizardFormThirdPage previousPage={this.previousPage} onSubmit={onSubmit}/>}
      </div>
    )
  }
}

WizardFormFirstPage,WizardFormSecondPage の onSubmit には this.nextPage を渡しています。

WizardFormThirdPage の onSubmit には onSubmit を渡しています。このサンプルでは親の onSubmit で渡している showResults が呼ばれる感じになります。

https://github.com/erikras/redux-form/blob/master/examples/wizard/src/index.js#L47

各画面ではバリデートメソッドの指定ができます。

export default reduxForm({
  form: 'wizard',                 // <------ same form name
  destroyOnUnmount: false,        // <------ preserve form data
  forceUnregisterOnUnmount: true,  // <------ unregister fields on unmount
  validate
})(WizardFormFirstPage)

また、formタグの部分は

<form onSubmit={handleSubmit}>

のような形で書くことになります。

https://github.com/erikras/redux-form/blob/master/examples/wizard/src/WizardFormFirstPage.js

Wizard Formでのバリデート

Use onSubmit to transition forward to the next page; this forces validation to run.

とあるように、基本的にonSubmitのタイミングでvalidate,AsyncValidateが実行されるのでライブラリの恩恵をうけるためにはこちらでまかなうのがベターです。

ただ、validateおよびAsyncValidateが実行されるのはonSubmit時以外に各入力フィールドにフォーカスが当たって外れたタイミングになります。

例えば入力必須項目の場合には

  • フォーカスが当たって外れたタイミングでバリデートが実行されて未入力であればエラーが表示
  • フォーカスも当たらず未入力の場合にはsubmitボタンが押されたタイミングでエラーが表示

という動きになります。ほぼこの動きでまかなえますが、数少ない例外としてフォーカスが当たったタイミングでバリデートをすぐに実行させたくないケースというのが存在します。

チェックボックスを3つ選んでください、みたいなケースなどです。このルールをvalidateに書くと、1つ目のチェックボックスにチェックを入れたタイミングでバリデートが実行されてしまいエラーが表示されるため、オイ!ということになってしまいます。

Submit Validationを使う

そんなケースのためにSubmit Validationが用意されています。

これ単体で使う場合には

http://redux-form.com/6.5.0/examples/submitValidation/

に書いてあるような方法で使います。

使い方のメインとしては以下のようにしてhandleSubmitの引数にバリデート用のメソッドを指定します。

<form onSubmit={handleSubmit(submit)}>

submitメソッドによるバリデートを実行→callbackを受け取ってhandleSubmitのメソッドを実行という順番で処理をしてくれるようです。

https://github.com/erikras/redux-form/blob/master/examples/submitValidation/src/SubmitValidationForm.js

Submit ValidationをWizard Formに組み込む

上記ルールに従えば、

WizardFormFirstPageとかでも

<form onSubmit={handleSubmit(submit)}>

と書けば、バリデートメソッドのsubmit実行後にnextPageメソッドが実行されるかと思いましたが、うまく行きませんでした。

最終的には以下のようにしました。

class WizardForm extends Component {
  const handleNextWithValidate = values => new Promise((resolve, reject) => {
      submit(values).then(() => {
        this.nextPage();
        resolve();
      });
    });

    const handleSubmitWithValidate = values => new Promise((resolve, reject) => {
      submitValidate(values).then(() => {
         this.props.onSubmit(values);
        resolve();
      });
    });

  nextPage() {
    this.setState({ page: this.state.page + 1 })
  }

  previousPage() {
    this.setState({ page: this.state.page - 1 })
  }

  render() {
    const { onSubmit } = this.props
    const { page } = this.state
    return (<div>
        {page === 1 && <WizardFormFirstPage onSubmit={handleNextWithValidate}/>}
        {page === 2 && <WizardFormSecondPage previousPage={this.previousPage} onSubmit={handleNextWithValidate}/>}
        {page === 3 && <WizardFormThirdPage previousPage={this.previousPage} onSubmit={handleSubmitWithValidate}/>}
      </div>
    )
  }
}

自前でsubmit(Submit Validation時に実行されるメソッド)後にcallbackでnextPageを実行するメソッドを用意してそちらを呼ぶように変更しました。

こうすることでバリデート後に次のページに遷移するという順番を制御できるようになります。

Redux Form は奥が深いですが、程よい設計になっていると思います。

あと気になるのはファイル添付などのmultipart処理と、

日本だとよくある入力画面→確認画面→完了画面のパターンとかでしょうか。(こっちは薄いラッパー作ってる人いそう)

multipartについてはこのへんで議論がされているようです。

https://github.com/erikras/redux-form/issues/734

https://github.com/erikras/redux-form/issues/71

今だと

  multipartForm : true, // <- handle this form as a multipart form and send to handleSubmit a FormData object populated by the form's values

でいけるかもしれません。

今回Redux Formまわりで色々と調べて参考になったURLを列挙して終わりとします。

multi checkbox form sample

https://github.com/erikras/redux-form/issues/635

React + ReduxのプロジェクトでRedux Formを使ったので使い方のまとめと注意点

日本語での詳しい使い方です。

http://ichimaruni-design.com/2016/10/react-redux-form/

React Native – Redux ecosystem(redux-form)

React Nativeにredux-formを組み込む例です。

http://qiita.com/shohey1226/items/b185e9795276de262c98

redux-form 慣れてきた

http://tnakamura.hatenablog.com/entry/2016/11/08/redux-form

React + TypeScriptのredux-formでバリデーションを行う

http://blog.okazuki.jp/entry/2016/01/21/124236

Material UI関連

Wizard Formにはstepperを使用すると相性が良いです。

http://www.material-ui.com/#/components/stepper

Wizard Formの画面遷移時にはrefresh-indicatorを使うことでかっこよくなります。

また、それ以外のメリットとして、画面遷移時にtransitionと組み合わせて使うことで、画面の一番上までスクロールしてくれるようになります。

stepperには画面遷移時にtransitionをセットすることができます。例えばダイアログ内で使用する場合、画面遷移時にtopまでスクロールしたいと言う時に組み合わせて使うといい感じになります。

http://www.material-ui.com/#/components/refresh-indicator

Wizard Formの実例(動作するサンプルサイトもあるのでよいです。)

https://github.com/DylanSimowitz/client-intake-form/tree/master/client/containers/Questionnaire

React.PropTypesで指定できる型一覧

https://facebook.github.io/react/docs/typechecking-with-proptypes.html