Yupとは
Yupとは入力値のバリデーションを担当するライブラリです。 入力欄1つひとつにバリデーションルールを定義する作業は、通常であれば面倒なものですが、Yupを使うとスッキリと定義でき とても便利なライブラリなのです!
相互依存したフォームのバリデーション
さて、このYupを用いて、相互依存するフォームのバリデーションを仕掛けたいと思いました。
GithubIssueやブログ等で調べてみると色々あって、 結局何を使えばいいのかわからなくなったので、 一旦整理してみたいと思います。
実装の前提と方針
実装の前提
実装の前提としては下記の画像にあるよな 誕生日の月と日をそれぞれいれるようなフォームを作りたいとします。 (普通、年月日の入力であればカレンダーで指定したりするのが普通ですがそこは無視でお願いします笑)
仕様の要件は、
- 月あるいは日は両方空白または両方選択済みの状態でなければならない
- 片方だけが入力されている場合、空白のボックスの下にエラー文を出す
といったものです。
いわゆる相互依存したフォームのバリデーションです。
実装方針
結論から言えば、実装するにはwhenメソッドまたはtestメソッドを使う必要があります。 これら2つのメソッドについては、こちらのドキュメントを確認してください。
Doc: https://github.com/jquense/yup#yup
実装方針はざっくり3つあります。
- 月と日の両方のフィールドでwhenメソッドを使用した上で、noSortEdgeに依存関係を明示する
- 片方をwhenメソッド,もう片方のフィールドではtestメソッドを使うといったパターン
- 両方ともtestメソッドを使うパターン
実装1: 月と日の両方のフィールドでwhenメソッドを使用する
この実装はGithubIssueなどで盛んに提案されている方法です。
今回のパターンだと下記のような実装になります。
const schema = yupJp .object() .shape( { birthMonth: yupJp .string() .label("誕生月") .trim() .nullable() .defined() .when("birthDay", ([birthDay], schema) => { return birthDay ? schema.required("誕生日がある場合は、月が必須です") : schema; }), birthDay: yupJp .string() .label("誕生日") .trim() .nullable() .defined() .when('birthMonth', ([birthMonth], schema) => { return birthMonth ? schema.required('誕生月がある場合は、日が必須です') : schema }), }, [["birthDay", "birthMonth"]] ) .required();
実は結構簡単に実装できます。 普通にwhenメソッドをそれぞれ利用して、第二引数に依存関係のフィールド名を入れています。 この第二引数をnoSortEdgeというらしい.....
ちなみに、これについてのドキュメントがほとんどないのは 作者曰く
It's a ugly escape hatch for dealing with cyclical dependencies. といって、それでドキュメントには書かなかったとか.
このパターンは割とメジャーな気がしますが、 noSortEdgeというドキュメントにないようなものを使うかどうかはそれぞれの判断になるのかなといった感じです。
実装2: whenとtestをそれぞれ使う
こちらは実装1と違って、依存関係を明示する必要はありません。
const schema = yupJp .object() .shape( { birthMonth: yupJp .string() .label("誕生月") .trim() .nullable() .defined() .when("birthDay", ([birthDay], schema) => { return birthDay ? schema.required("誕生日がある場合は、月が必須です") : schema; }), birthDay: yupJp .string() .label("誕生日") .trim() .nullable() .defined() .test( "birthDay", "誕生月がある場合は、日が必須です", (value, testContext) => { const { birthMonth } = testContext.parent; return value !== null || birthMonth === null; } ), } ) .required();
これはこれでいいのですが、 testメソッドではschemaオブジェクトをもってこれないので バリデーションルールの表現のためにコードが長くなりがちというデメリットがあります。
実装3: 両方testを使う
これは上記でも記載しましたが、 バリデーションルールの表現のためにコードが長くなりがちというデメリットがあるので 回避したほうが良さそうです。
結論
個人的な意見としては、実装1がわかりやすかったりしますが、 noSortEdgeが割とマイナーだったりするので無難に実装2のパターンでもいいかも。 動き的にも同じなので、。。。。。