はじめに
株式会社エアークローゼットのプロダクトグループでエンジニアをしている工藤と申します。
この記事はエアークローゼット Advent Calendar 2022 の5日目の記事になります
今回はReactのバリデーションライブラリであるYupを使ってバリデーションを細分化した話をします。
UXを最重要視するエアークローゼットらしい記事になっていますので、興味のある方はぜひご覧ください。
この記事のまとめ
- Yupのバリデーションは
.matches()
を繋ぐことで細分化できる - 細分化を目的にすると
.matches()
の仕様は多少ややこしいので注意が必要
やりたかったこと
今回は、エアークローゼットのサービスサイトにおける「無料登録導線」のUXを改善するプロジェクトにアサインされ、その実装を担当することになりました。
ビジネス側の要望
- 無料会員登録のUXを改善するため、それまで1パターンだったバリデーションメッセージを16パターンに出し分けてほしい
- エラーメッセージは登録ボタン押下時ではなく、1文字入力する度に出してほしい
エンジニア(私)の考え
- 極力、既存のコードを破壊したくない(かなり重要な導線であり、バグが発生した際のダメージが大きい)
- 該当箇所はYupで実装されていたため、実装の過程で苦手な正規表現を勉強したい
※ちなみに↓が実装前の動画です。
エラーがあっても全て同じメッセージで、何が足りないのか全くわかりません...
やったこと
Yup.string.matches()
を数珠繋ぎにすることで、パスワードを細分化しました
実際のコードと実装後の動画を以下に貼っておきます。
実際のコード
Yup.object().shape({
// ...(一部省略)
password: Yup.string()
matches(/^((?=.*[a-z]).*|^[0-9]+$|^[A-Z]+$|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_LOWER_CASE)
.matches(/^((?=.*[a-z]).*|^[0-9]+$|^[A-Z]+$|.{0,7})$/, ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_LOWER_CASE)
.matches(/^((?=.*[A-Z]).*|^[0-9]+$|^[a-z]+$|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_UPPER_CASE)
.matches(/^((?=.*[A-Z]).*|^[0-9]+$|^[a-z]+$|.{0,7})$/, ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_UPPER_CASE)
.matches(/^((?=.*[0-9]).*|^[A-Z]+$|^[a-z]+$|.{8,})$/, ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_NUM)
.matches(/^((?=.*[0-9]).*|^[A-Z]+$|^[a-z]+$|.{0,7})$/, ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_NUM)
.matches(/^([^0-9].*|(?=.*[a-z])(?=.*[0-9]).*|(?=.*[A-Z])(?=.*[0-9]).*|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_UPPER_CASE_AND_LOWER_CASE)
.matches(/^([^0-9].*|(?=.*[a-z])(?=.*[0-9]).*|(?=.*[A-Z])(?=.*[0-9]).*|.{0,7})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_UPPER_CASE_AND_LOWER_CASE)
.matches(/^([^a-z].*|(?=.*[0-9])(?=.*[a-z]).*|(?=.*[A-Z])(?=.*[a-z]).*|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_NUM_AND_UPPER_CASE)
.matches(/^([^a-z].*|(?=.*[0-9])(?=.*[a-z]).*|(?=.*[A-Z])(?=.*[a-z]).*|.{0,7})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_NUM_AND_UPPER_CASE)
.matches(/^([^A-Z].*|(?=.*[a-z])(?=.*[A-Z]).*|(?=.*[0-9])(?=.*[A-Z]).*|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8_AND_NUM_AND_LOWER_CASE)
.matches(/^([^A-Z].*|(?=.*[a-z])(?=.*[A-Z]).*|(?=.*[0-9])(?=.*[A-Z]).*|.{0,7})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_NUM_AND_LOWER_CASE)
.matches(/^(^[a-z]+$|^[A-Z]+$|^[0-9]+$|(?!.*[0-9]).*|(?!.*[a-z]).*|(?!.*[A-Z]).*|.{8,})$/,ERROR_MESSAGE.INVALID_PASSWORD_REQUIRE_8)
// ...(一部省略)
})
実際の動画
文字を入力するたびにエラーメッセージが変化し、何が足りないのかが一目でわかるようになりました 🎉
大変ポイント
1. Yupの仕様が(個人的に)直感的でない
Yupの.matches()
は、第1引数に「入力を許可する」正規表現パターンを、第2引数に「第1引数のパターンに一致しなかった場合のメッセージ」をとります。このため、例えばabc
のような英小文字しか入力されていないパターンのエラーメッセージを出す場合、第1引数には 「英小文字だけ」以外の全てにマッチする という正規表現を書くする必要があるのです (下図)。
.matches()
を数珠繋ぎにしてバリデーションを実装するにあたっては許可しないパターンを書けたら直感的だったのですが、生憎.notMathes()
のような関数は存在しなかったため、その点で非常に苦労しました。
2. 正規表現の検証が難しい
正規表現が必要な実装をした経験がなかったため、初めは全て 1.コードを書く
→ 2.実際にパスワードを打ち込んでみる
の流れで実装をしていましたが、検証に時間がかかりすぎてものすごく非効率になっていました。
そこで、弊社エンジニアの@baochanh18が社内LT会で使用していたregular expressions 101に必要なパターンを打ち込んで検証するようにしたところ、検証速度を圧倒的に早めることができました。
あとがき
最後までご覧いただきありがとうございました🙇♂️
エアークローゼット Advent Calendar 2022はまだまだ続きますので、ぜひ他のエンジニア, デザイナー, PMの記事もご覧いただければと思います。
次回は今年の11月にエアクロにジョインしてくれた@victory1011さんが担当しますので、是非お楽しみに!