shimotsu tech

Webフロントエンドエンジニア @ to-R inc.

React Hook Form の setFocus() 時に "TypeError: Cannot read properties of undefined (reading '_f')" のエラーが出る

React Hook Form を使ったフォームの実装において、フォームの任意の要素にフォーカスを当てる setFocus() メソッドの実行時に TypeError: Cannot read properties of undefined (reading '_f') のエラーが出てしまうことがありました。

useForm - setFocus | React Hook Form - Simple React forms validation

今回はその原因と解決策をメモがてら書いていきます。

やりたいこと

やりたかいこととしては、とあるタイトルとそれを「編集する」ボタンを置いて、そのボタンを押下したら編集のためのフォームを表示する、というものです。

簡略化すると、以下のようなイメージです。

const PostTitle: React.FC<props> = ({ post }) => {
  const { title } = post
  const [isEditing, setIsEditing] = useState(false)

  const { register, handleSubmit, setFocus } = useForm<Inputs>()

  const handleEdit = () => {
    setIsEditing(true)
    setFocus('title') // ここでエラーが発生
  }

  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    // 送信処理
  }

  return (
    <>
      <div>
        {isEditing ? (
          <form onSubmit={handleSubmit(onSubmit)}>
            <label></label>
            <input
              {...register('title')}
              defaultValue={title}
            />
            <button>保存</button>
            <button
              onClick={() => setIsEditing(false)}
            >
              キャンセル
            </button>
          </form>
        ) : (
          <>
            <h1>{title}</h1>
            <button onClick={handleEdit}>
              編集
            </button>
          </>
        )}
      </div>
    </>
  )
}

そして、フォームを表示する際、デフォルトでフォームにフォーカスが当たるようにしたかったので、setFocus('title') を実行したところ、エラーが発生した、というわけです。

原因

原因としては、 Cannot read properties of undefined (reading '_f') と書いてあるように、プロパティが undefined となっており参照できないことにあります。

おそらくの予想ですが、setFocus() メソッドは、その引数に入力フィールドの名前を(今回の場合だと title)を渡すのですが、handleEdit 関数の実行時、setIsEditing(true) となりフォームがレンダリングされた直後だと、特定のフィールドを参照できないことが原因なのかなと思いました。

解決策

今回は、 setTimeout を使い、setTimeout(func, 0) として解決しました。

0ミリ秒遅延させることで、setFocus() の実行を別のスケジューラのタイミングで実行するようにし、title という入力フィールドを持つ要素に無事アクセスできるようになりました。

const PostTitle: React.FC<props> = ({ post }) => {
  const { title } = post
  const [isEditing, setIsEditing] = useState(false)

  const { register, handleSubmit, setFocus } = useForm<Inputs>()

  const handleEdit = () => {
    setIsEditing(true)

    setTimeout(() => {
      setFocus('title') // これでエラーが発生することなく実行されるようになった
    }, 0)
  }

  const onSubmit: SubmitHandler<Inputs> = async (data) => {
    // 送信処理
  }

  return (
    <>
      <div>
        {isEditing ? (
          <form onSubmit={handleSubmit(onSubmit)}>
            <label></label>
            <input
              {...register('title')}
              defaultValue={title}
            />
            <button>保存</button>
            <button
              onClick={() => setIsEditing(false)}
            >
              キャンセル
            </button>
          </form>
        ) : (
          <>
            <h1>{title}</h1>
            <button onClick={handleEdit}>
              編集
            </button>
          </>
        )}
      </div>
    </>
  )
}

参考リンク

ja.javascript.info