🎃

ReactHookForm setErrorでsubmitを止めたい

2022/09/26に公開

環境

React Hook Form v17

useForm の setError

https://react-hook-form.com/api/useform/seterror/
エラーを手動で設定できる。

setError('registerInput', { type: 'custom', message: 'custom message' })

何がしたかったのか

emailの入力値がすでにDBに登録されている場合に、他のバリデーションと同様にエラー表示し、submitさせないようにしたい。

  • 入力フォーム(input要素)にonBlurを設定
  • 入力値をwatchで取得して存在確認用のAPIを呼ぶ
  • APIの結果によってエラーを設定する(ここでsetErrorを用いる)

躓いた点

  • setErrorを設定するだけではsubmitが走ってしまう。

似たようなことで困っているgithubのissue。
https://github.com/react-hook-form/react-hook-form/issues/226

そもそもバリデーションを設定するものではない。本来の使いどころは以下のような場合の時。

Can be useful in the handleSubmit method when you want to give error feedback to a user after async validation. (ex: API returns validation errors)

解決方法

バリデーションに'email'の派生に当たる'email_exists'を作成して追加する。
yupを使用しているので以下のような感じの定義となる。

email_exists: yup
    .bool()
    .oneOf([false], 'すでに登録されている値です')
    .default(false),
処理の流れ
  • emailの入力。フォーカスが外れたときに onBlurEmail() の呼び出し(以下のサンプル)
  • apiの実行(既存な値があるという結果返ってくる)
  • 'email_exists' に対して、setValue & setError
  • setError があると入力後すぐにエラーを画面に表示できる
  • 'email_exists' でバリデーションが効いて onSubmit に至らない
  const onBlurEmail = () => {
    //初期化
    clearErrors('email_exists')
    setValue('email_exists', false)

    //api呼び出しは省略

    setValue('email_exists', true)
    setError('email_exists', {
      type: 'custom',
      message: 'すでに登録されている値です',
    })
  }

失敗した方法

shouldFocusを設定する方法。
  • api呼び出しの結果、エラーを表示
  • shouldFocusが効いているので、emailのinput要素にフォーカスが当たる
  • そのままのエラーを残した状態でフォーカスを外すとonBlurが走る。再度apiが呼び出される
  • 結果は変わらないのでエラー表示。フォーカスが戻ってくる(無限ループ)

*正しくない値が入力され続ける限りフォーカスされ続ける。navのリンクなど、全く関係のない個所を押そうとしても、フォーカスが外れることで再度実行されるため、無限ループに陥る。

setError('registerInput', { type: 'custom', message: 'custom message' }, { shouldFocus: true })
import { useForm } from "react-hook-form";

const App = () => {
  const { register, setError, formState: { errors } } = useForm();
  const handleBlur = () => {
    //api呼び出しは省略
    setError('email', { type: 'custom', message: 'custom message' }, { shouldFocus: true });
  }

  const onSubmit = () => {(略)}

  return (
    <form>
      <input {...register("email")} onBlur={handleBlur} />
      {errors.email && <p>{errors.email.message}</p>}

      <button type="submit" >submit</button>
    </form>
  );
};

Discussion