2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

エアークローゼットAdvent Calendar 2022

Day 5

Yup.string().matches()でバリデーションを細分化した話

Last updated at Posted at 2022-12-04

はじめに

株式会社エアークローゼットのプロダクトグループでエンジニアをしている工藤と申します。
この記事はエアークローゼット Advent Calendar 2022 の5日目の記事になります

今回はReactのバリデーションライブラリであるYupを使ってバリデーションを細分化した話をします。
UXを最重要視するエアークローゼットらしい記事になっていますので、興味のある方はぜひご覧ください。

この記事のまとめ

  • Yupのバリデーションは.matches()を繋ぐことで細分化できる
  • 細分化を目的にすると.matches() の仕様は多少ややこしいので注意が必要

やりたかったこと

今回は、エアークローゼットのサービスサイトにおける「無料登録導線」のUXを改善するプロジェクトにアサインされ、その実装を担当することになりました。

ビジネス側の要望

  • 無料会員登録のUXを改善するため、それまで1パターンだったバリデーションメッセージを16パターンに出し分けてほしい
  • エラーメッセージは登録ボタン押下時ではなく、1文字入力する度に出してほしい

エンジニア(私)の考え

  1. 極力、既存のコードを破壊したくない(かなり重要な導線であり、バグが発生した際のダメージが大きい)
  2. 該当箇所はYupで実装されていたため、実装の過程で苦手な正規表現を勉強したい

※ちなみに↓が実装前の動画です。
エラーがあっても全て同じメッセージで、何が足りないのか全くわかりません...
9b8db476786f55a2abe837135ffb36e6.gif

やったこと

Yup.string.matches()を数珠繋ぎにすることで、パスワードを細分化しました :pencil2:
実際のコードと実装後の動画を以下に貼っておきます。

実際のコード

index.tsx
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)
  // ...(一部省略)
})

実際の動画

文字を入力するたびにエラーメッセージが変化し、何が足りないのかが一目でわかるようになりました 🎉
53578bcd3d629578e4f3f4fa5d2badfb.gif

大変ポイント

1. Yupの仕様が(個人的に)直感的でない

Yupの.matches()は、第1引数に「入力を許可する」正規表現パターンを、第2引数に「第1引数のパターンに一致しなかった場合のメッセージ」をとります。このため、例えばabcのような英小文字しか入力されていないパターンのエラーメッセージを出す場合、第1引数には 「英小文字だけ」以外の全てにマッチする という正規表現を書くする必要があるのです (下図)。

.matches()を数珠繋ぎにしてバリデーションを実装するにあたっては許可しないパターンを書けたら直感的だったのですが、生憎.notMathes()のような関数は存在しなかったため、その点で非常に苦労しました。
Monosnap LT_2022_08_25 2022-12-04 19-46-11.png

2. 正規表現の検証が難しい

正規表現が必要な実装をした経験がなかったため、初めは全て 1.コードを書く2.実際にパスワードを打ち込んでみる の流れで実装をしていましたが、検証に時間がかかりすぎてものすごく非効率になっていました。

そこで、弊社エンジニアの@baochanh18が社内LT会で使用していたregular expressions 101に必要なパターンを打ち込んで検証するようにしたところ、検証速度を圧倒的に早めることができました。
Monosnap regex101: build, test, and debug regex 2022-12-04 20-09-25.png

あとがき

最後までご覧いただきありがとうございました🙇‍♂️

エアークローゼット Advent Calendar 2022はまだまだ続きますので、ぜひ他のエンジニア, デザイナー, PMの記事もご覧いただければと思います。

次回は今年の11月にエアクロにジョインしてくれた@victory1011さんが担当しますので、是非お楽しみに!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?