an odd fellow

仕事のメモ

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つ思いついた

  1. StoreにFormごとのstateを持たせる
  2. 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行そのままだった。その他に気付いたことは、handleChangehandleResetなど良く使うハンドラは既に定義されていて一般的な振る舞いをやってくれる。また、特殊な動きがしたければ簡単に上書きも可能というもののようだった。中間状態はFormikの定義しているvaluesが担うようだ。

背景で説明したread・write・execのFormを実装するならば、wirhFormikの mapPropsToValues で配列から辞書に変換して、handleSubmitvaluesから辞書を取り出して配列に戻して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のonChangehandleChangeを渡していたが動かなかった。handleChange<input type=text>のような逐次変わる値を追従させるときに使うようだ。<input type=checkbox>みたいなやつはsetFieldValueを使う。 また、setFieldValueの第一引数は厄介だった。withFormicmapPropsToValuesで作っているpropsの構造を文字列で書く必要がある。propsの構造を複雑にしていると大変だ。

setFieldValueの使い方を良くよむこと。

GitHubのissueにcheckbox使う場合の例がcodesandboxで置いてくれているのが参考になった。 How to populate array with checkboxes? · Issue #360 · jaredpalmer/formik · GitHub

上書きできるのはhandleSubmitだけ?

↑でハマっていたときにhandleChangewithFormicで上書きしてしまえばいいのでは?と思ってためしたができなかった。上書きできるのはhandleSubmitだけらしい。

というわけでFormikでやりたいことができていい感じだった。おすすめ。