ITの隊長のブログ

ITの隊長のブログです。Rubyを使って仕事しています。最近も色々やっているお(^ω^ = ^ω^)

【わからない】react-hook-formとYupで複数フィールドの重複をチェックするバリデーションとエラーを表示するフォーム

スポンサードリンク

ほぼ1日かけて探したけどわからないので供養

const uniqueId = (value, context) => {
  const [_, parent] = context.from
  const filedNames = ['hogehoge1', 'hogehoge2', 'hogehoge3']
  const list = [
    parent.value.hogehoge1.id,
    parent.value.hogehoge2.id,
    parent.value.hogehoge3.id,
  ]
  console.log(list)
  const r = !list || list.length === new Set(list).size

  if (r) return true
  const errors = []
  const duplicateErrorMessage = 'IDは重複してるので入力できません。'
  // とりあえず雑に組む(ry
  const a1 = list[0] === list[1]
  const a2 = list[0] === list[2]
  const a3 = list[1] === list[2]
  if (a1) {
    errors.push(new yup.ValidationError(duplicateErrorMessage, 'type', `${filedNames[0]}.id`))
    errors.push(new yup.ValidationError(duplicateErrorMessage, 'type', `${filedNames[1]}.id`))
  }
  if (a2) {
    if (!a1) {
      errors.push(new yup.ValidationError(duplicateErrorMessage, 'type',  `${filedNames[0]}.id`))
    }
    errors.push(new yup.ValidationError(duplicateErrorMessage, 'type', `${filedNames[2]}.id`))
  }
  if (a3) {
    if (!a1) {
      errors.push(new yup.ValidationError(duplicateErrorMessage, 'type', `${filedNames[1]}.id`))
    }
    if (!a2) {
      errors.push(new yup.ValidationError(duplicateErrorMessage, 'type', `${filedNames[2]}.id`))
    }
  }
  return new yup.ValidationError(errors)
}

const defaultSchema = {
  hogehoge1: yup.object({
    id: yup.string().required('IDを入力してください') // TODO: 他のidと重複しちゃだめ
      .test('unique_id', uniqueId),
    location: yup.string(),
  }),
  hogehoge2: yup.object({
    id: yup.string().required('IDを入力してください') // TODO: 他のidと重複しちゃだめ
      .test('unique_id', uniqueId),
    location: yup.string(),
  }),
  hogehoge3: yup.object({
    id: yup.string().required('IDを入力してください') // TODO: 他のidと重複しちゃだめ
      .test('unique_id', uniqueId),
  }),
}

const schema = yup.object(defaultSchema)
// 省略
  const {
    register,
    handleSubmit,
    formState: { errors },
    watch,
    control,
  } = useForm<CreateInput>({
    resolver: yupResolver(schema),
  })

やりたかったこと

  • 1 特定の複数フィールドのidが重複していないかバリデーションをかけたい
  • 2 a,b,cとあった場合、a,bのフィールドが重複している場合、a,bのフィールドにバリデーションエラーメッセージを表示したい
  • 3 a,bのフィールドが重複してエラーメッセージが表示されている場合、aを修正するとa,bそれぞれのフィールドからバリデーションエラーメッセージが非表示になる

できなかったこと

  • 1,2はできた、3ができなかった
    • aを治すとaは非表示になるが、bはバリデーションが動かないっぽいので表示されたままになっている

できなかったことについて試したこと・探したこと

  • ValidationErrorを返したらエラーが表示されるのでreturnしたらクリアされるやつとかないかな?
    • なさそう
  • Yupを外部から無理やりでもいいからフィールド指定したらバリデーション叩いてくれるやつないかな
    • なさそう(引数食わしたらバリデーション実行できるやつはあるけど、状態が変化するやつじゃないのでFormまで伝播(?)はしない
  • react-hook-formのtriggerやFormStateを駆使してyupのバリデーションを叩いたり、errorsを書き換えようとした
    • Yupの世界からでてしまうので嫌だなと思ったのと、バリデーション実行するとレンダリングが複数走るので実装が複雑になっちゃうのと、Yup → react-hook-formがYup ↔ react-hook-form の依存になるのでこれも嫌だなと思ったため諦めた

学んだこと

  • schemaをネストする
    • ↑のように hogehoge1_id でもできるけど、ネストして hogehoge1.id でもできることがわかった
    • ただその場合、 ネストしたobjectを yup.object() にわたす必要がある。じゃないとエラーになる
  • ValidationErrorで返すと複数エラーメッセージを返せる
    • CreateErrorで返すサンプルが多かったんですが、pathを複数もたせる(複数フィールドをバリデーションエラーにしたい)場合どうすれば?をぐぐると、ValidationErrorを配列に詰めて最後にValidationErrorに食わせて返すでおk
  • フィールドに紐付けず、schemaから .test メソッドなどを生やしてバリデーションを書くことができる
    • 全体の値や一部フィールドの値を使ってバリデーションを実行するってこともできることがわかった

(追記)実装パターン追加

いずれも解決できず。。。orz

refを使う

他フィールドを参照して自分フィールドの値と同じかどうか確認する

  hogehoge1_id: yup.string().required('hogehoge1 IDを入力してください')
    .notOneOf([yup.ref('hogehoge2_id')], 'hogehoge2_idと同じ値です')
    .notOneOf([yup.ref('hogehoge3_id')], 'hogehoge3_idと同じ値です')

ただ、hogehoge2_idが重複しててもエラーメッセージは hogehoge3_idと同じ値です になる。なぜか上書きされる

whenを使う

whenは1つしか引数与えられないと思いきや複数行けた。良い!

  hogehoge1_1: yup.string()
    .when(['hogehoge2_id', 'hogehoge3_id'], (hogehoge2_id, hogehoge3_id, schema) => {
      return schema.notOneOf([hogehoge2_id, hogehoge2_id], 'hogehoge2 ID、またはhogehoge3 IDは同じ値で入力できません。')
    })
    .required('hogehoge1 Device IDを入力してください'),

TypeScriptだと下記エラーがでる。わからないのでほっといている

型 '(hogehoge2_id: any, hogehoge3_id: any, schema: any) => any' の引数を型 'ConditionOptions<StringSchema<string | undefined, AnyObject, string | undefined>>' のパラメーターに割り当てることはできません。
  型 '(hogehoge2_id: any, hogehoge3_id: any, schema: any) => any' を型 'ConditionBuilder<StringSchema<string | undefined, AnyObject, string | undefined>>' に割り当てることはできません。ts(2345)

で?結局できたの?

できませんでした。 実装はスッキリさすことができたが、 できなかったこと をクリアすることができなかった。