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> </> ) }