redux-formで独自のsubmitハンドラを実装する

redux-form という html 上の form エレメントを redux の store と紐づけて(簡単に?)react 上で使用できるというライブラリがあります。

このライブラリの良いところは自分自身で store を定義しなくとも redux-form を用いることで自動的に store 上でデータが管理されるとともに option 設定も豊富で色々な機能を簡単に実装することができる点だと思います。

反面ちょっと分かりづらい点も多いというか、微妙な仕上がりになっている点もあるように見受けられ、学習コストもそれなりに高いです。

今回は掲題の通り、redux-form を用いて submit 処理を行う際に任意の処理を差し込むことができず、ハマったことがあり、その話について取り上げます。

執筆時点での最新版は 8.1.0 、また本記事で取り上げる私が確認したバージョンは 7.4.2 となっておりますのでご了承ください。

やりたかったこと

とある post データを送信する際に、redux-form 上で送信されるフォーム情報に対して、CSRF 対策を行うために csrf-token をのせることを実装しようとしておりました。

詰まったこと

csrf-token は別の reducer として実装されている redux-store 上に保存されるため、redux-form 上のコンテキストで保持されるデータとは別軸で管理されます。そのため redux-form 上で他の store 情報を参照するのかということがわからず、かなり詰まりました。

redux-formの実装時の概要

redux-form では submit する際に form エレメントの属性に指定した onSubmit がコールされます




<form onSubmit={this.props.handleSubmit}>

Getting Started を見るとこのあたりのサンプルコードが書いてあります。また props api を確認することでその内部挙動がわかります。

要約しますと onSubmit で指定されたのは component に実装されている this.props.handleSubmit となります。これは reduxForm を component と紐付けることで実装されるハンドラになります。

また handleSubmit はコールされると this.props.onSubmit をコールします。またその際にバリデーションされた form value が引数として渡されます。実にキモいです。なぜこういった構成をしているのかはよくわかりません。あと form の属性値とコールされるメソッド名が同一なのでとても混乱します。

どうやって onSubmit に介入するのか

この際に先程にも述べたとおり store 上に保存されている csrf-token を差し込んで上げる必要があります。

はじめに this.props.onSubmit がコールされたあとで差し込める余地があるかということを検討しました。しかしながら onSubmit は通常の関数かまたは reducer 内部の処理になります。そのため store の値を参照することができません。store と接続されているコンポーネント側で何かしら解決を図る必要があります。

次に form の onSubmit 属性に指定している処理内部でどうにかして任意の処理を挟み込めないか(ここでは store に保存されている csrf-token の値を参照する)ということを考えました。

ここでは console に値を吐き出しているだけですが、イメージとしては下記のような形で実装できないかと考えました




<form onSubmit={values => {
    console.log(values);
}}>

しかしながら、こうするとうまくいきません。何故か onSubmit が発火した時点で画面がリロードされる現象に遭遇します。

github issue 上でも同じような事象が報告されるらしく、最終的に独自コンポーネントでラッピングするだの何だのというちょっと意味不明な意見が提示されてしかもそれが支持を得ています。

はっきり言ってここまで大人数が混乱しているのはAPIの設計が悪いです。

解決した方法

解決方法がないのかと思いましたが、いろいろ試行錯誤していく中で目的のことを解決できたので紹介します。

onSubmit で何が起こっていたのかを確かめる

先程のサンプルコードで画面のリロードが起こった件に関しては、 action 属性が未指定の form を submit した際にも同じような挙動となります。

これはつまり redux-form のコンテキスト外部で form が submit されてしまっているということだと推測しました。

コードを下記のように変更してみます。




<form onSubmit={values => {
    values.preventDefault();
    console.log(values);
}}>

すると画面はリロードされなくなります。また values をコンソールに出力していますが、これはイベント変数であることがわかりました。

すると当たり前なのですが単純に onSubmit は redux-form 関係なく、react のコンテキストで忠実に submit イベントを発行しているハンドラーを実装しているだけということになります。

ここからさらに推測すると redux-form でガイドされている this.props.handleSubmit に関しては onSubmit イベントを受け取って form の値の検証などを行った form の変数値を this.props.onSubmit メソッドに引数として渡してコールしている。というように推測できます。

form APIを再確認する

ここまで挙動が確認できたので、再度 form API を見てみます。以下引用しますが

  • performing your submission from inside your form component by passing onSubmit={this.props.handleSubmit(this.mySubmitFunction)} to your component

との記載があり任意の submitHandler を差し込めるようです。また明記はされていませんが、おそらくこの submitHandler は 標準の this.props.onSubmit と同様に form 値を引数として受け取る関数であることが考えられます。

そのため、引数として form 値を引数として受け取るクロージャを実装して handleSubmit に渡してあげることで、当然 component 内部のコンテキストから他の store 値が参照できます。

最終的に下記のような実装としました。




<form onSubmit={handleSubmit(values => {
    this.props.postForm(values, this.props.session.csrfToken);
})}
>

this.props.postForm は props に connect した redux アクションです、また this.props.session も props に connect した store 値になりますので何でも構いません。

こういった手法で store 値を reudx-form の submit コンテキスト内部で参照することができるようになりました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください