formikを使ってみる
前提
$ node --version v.9.11.1
React, ReduxでSPAを作っている。
背景
connectで渡されたpropsをComponentで加工してFormを作っている場合、Formを触って変更された値をStoreまで持っていっても、その値はあくまでもFormのみで有効な値であるから、Storeに反映させる変更は無いことに気付いた。
例えば、read・write・ execの3つの権限をチェックボックスで付けたり外したりできるFormを考えるとわかりやすい。connectでは3つの権限のうち、既に付いている権限だけが ["read", "exec"]
のように配列で渡されてくる。Componentではこれを {read: true, write: false, exec: true}
のような辞書に変換してチェックボックスを作る。ここからが重要で、チェックボックスをクリックすると onChange
に登録されたイベントハンドラには {read: false}
のような辞書が引数として渡されることになる。しかし、これをStoreまで引きずっていってもどうしようもないのである。Storeが欲しいものはread・write・execの全ての状態がどうなったかという、Formで最終的に生成された状態であって、「readがfalseになりました」というような断片的な情報には興味が無い。もちろん {read: false}
を受け取って ["read", exec"]
を ["exec"]
に変更することはできる。しかし、今回はたまたま簡単にハンドリングできるだけであって、より複雑なFormでは現実的でないだろう。
解消方法としては2つ思いついた
- StoreにFormごとのstateを持たせる
- reactのstateを使う
1は論外だろう。あり得ない。というわけで2番を採用する方向でReduxを利用しているときのReactのstateの利用有無について調べていたら、どうやらこれがReact界隈でいうところの Global State vs Local State という議論らしい。
ReduxにおけるGlobal stateとLocal stateの共存 - LIVESENSE ENGINEER BLOG
まあ現実的にはLocal Stateを解禁するしかないだろうと思ったが、一応社内のフロントエンジニアに聞きに行ったら今は「Ephemeral Stateという概念があるよ」と言ってFromikを教えてくれた。
GitHub - jaredpalmer/formik: Build forms in React, without the tears 😭
3行でどういうものか説明して、と頼んだらこういうものだと言う。
- formのような一時的なstate管理はreduxで管理しなくていいよ
- 代わりにpropsをformのstateにmappingしてくれる機能を用意したよ
- formを送信するタイミングでformのstateをreduxに流し込むよ
つまり、ComponentのstateでもなくStoreのstateでもない中間層を用意してそこにFormの中間状態を保持してsubmitのタイミングでStoreに渡してくれるようだ。
欲しかったものっぽいので試してみる。
BasicForm
公式のデモのBasicがシンプルなフォームを作っていてわかりやすかったので、適当に不要な所を省いて写経した。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
使ってみた感じは彼が教えてくれた3行そのままだった。その他に気付いたことは、handleChange
やhandleReset
など良く使うハンドラは既に定義されていて一般的な振る舞いをやってくれる。また、特殊な動きがしたければ簡単に上書きも可能というもののようだった。中間状態はFormikの定義しているvalues
が担うようだ。
背景で説明したread・write・execのFormを実装するならば、wirhFormikの mapPropsToValues
で配列から辞書に変換して、handleSubmit
でvalues
から辞書を取り出して配列に戻してActionCreatorを呼び出すようになるかなと思った。
PermissionSelectorForm
背景に書いた、read・write・execのFormにFormikを適用してみたい。このFormをPermissionSelectorFormと呼称することにする。
Reactだけで実装
多分こんな感じになる。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
Formikを適用する
多分こうなる。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
ハマりどころ
handleChangeかsetFieldValueか
最初はinputのonChange
にhandleChange
を渡していたが動かなかった。handleChange
は<input type=text>
のような逐次変わる値を追従させるときに使うようだ。<input type=checkbox>
みたいなやつはsetFieldValue
を使う。
また、setFieldValue
の第一引数は厄介だった。withFormic
のmapPropsToValues
で作っているprops
の構造を文字列で書く必要がある。props
の構造を複雑にしていると大変だ。
setFieldValueの使い方を良くよむこと。
GitHubのissueにcheckbox使う場合の例がcodesandboxで置いてくれているのが参考になった。 How to populate array with checkboxes? · Issue #360 · jaredpalmer/formik · GitHub
上書きできるのはhandleSubmitだけ?
↑でハマっていたときにhandleChange
をwithFormic
で上書きしてしまえばいいのでは?と思ってためしたができなかった。上書きできるのはhandleSubmit
だけらしい。
というわけでFormikでやりたいことができていい感じだった。おすすめ。