これは GLOBIS Advent Calendar 2023 6日目の記事です。
あいさつ
ご縁あってグロービスさんに参画させていただいています。
よしにゃんです。
アドベントカレンダーに参加させていただきます。
@hackpopo さんからバトンを受け取り、書いています。
この記事を書くモチベーション
実務で初めてreactとreact-hook-formを使っているのですが、useFormとuseFormContextの関係というか使い分けがあんまり分かりませんでした。
公式ドキュメントを見ても端的に書かれているので、僕には理解できなかったです。
同じように思っているひとがいるかもしれないと記事を書いています。
react-hook-formとは
ぱっと読んだ感じ、formまわりの処理を簡潔にし、パフォーマンス(実行速度?)をあげるためのものです。
生reactで実直に書くよりもreact-hook-formを使うことで処理を簡潔に隠蔽してくれてそうですね。
他に、いいライブラリがあったらコメント欄で教えてください。
useFormとuseFormContextとは
react-hook-formのAPIです。
フォーム周りのカスタムフックですね。
結論
useFormはformの状態管理をしている。
useForm単独でも使用可能だが、複雑なformの場合は、useFormContextを使い、useFormの状態やメソッドをuseFormContextを通じて子コンポーネントに渡すことができる。
使いわけの簡単なコツ
Formが単一コンポーネントで済むような規模ならuseFormのみを使う。
Formが複数のコンポーネントにまたがる場合、useFormContextを使って複数のコンポーネントに分けるとことで、ソースの見通しを良くし、小さく作り、再利用しやすくする。
使い方
useFormを定義した親コンポーネントからFormProviderで子コンポーネントを囲むことで子コンポーネントでメソッドなどが使用可能となる。子コンポーネントではuseFormContextから状態やメソッドをもらって使用する。
サンプルコード
実践的な内容にしたいので、typescriptで型定義・バリデーション定義にzodを使っています。
input要素はひとつなので、そこはシンプルになっています。
Formが単一コンポーネントで済む場合
// 親の親コンポーネント(参考までに書きました)
import React from 'react'
import { SampleForm } from './SampleForm'
const App = () => {
return (
<div>
<SampleForm />
</div>
)
}
export default App
// 親コンポーネントのみ
import * as react from "react";
import { useForm, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(1, '名前を入力してください')
})
type UserData = z.infer<typeof userSchema>
export const SampleForm:React.FC = () => {
const { register, handleSubmit, formState: { errors } } = useForm<UserData>({
resolver: zodResolver(userSchema)
})
const onSubmit: SubmitHandler<UserData> = data => {console.log(data)}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">なまえ</label>
<input id="name" {...register('name')} />
<p>{errors.name?.message}</p>
</div>
<button type="submit">送信</button>
</form>
)
}
単純なformなのでuseFormのみとなっています。
Formが複数のコンポーネントにまたがる場合
// 親コンポーネント
import * as react from "react";
import {useForm, SubmitHandler, FormProvider} from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from 'zod'
import NameInput from "./NameInput";
const userSchema = z.object({
name: z.string().min(1, '名前を入力してください')
})
export type UserData = z.infer<typeof userSchema>
export const SampleForm:React.FC = () => {
const methods = useForm<UserData>({
resolver: zodResolver(userSchema)
})
const onSubmit: SubmitHandler<UserData> = data => {console.log(data)}
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<NameInput />
<button type="submit">送信</button>
</form>
</FormProvider>
)
}
// 子コンポーネント
import * as react from "react";
import { useFormContext } from "react-hook-form";
import { UserData } from "./SampleForm"
import React from "react";
const NameInput: React.FC = () => {
const { register, formState: { errors } } = useFormContext<UserData>()
return (
<div>
<label htmlFor="name">なまえ</label>
<input id="name" {...register('name')} />
<p>{errors.name?.message}</p>
</div>
)
}
export default NameInput
FormProviderを通じて、子コンポーネント(NameInput)にuseFormが持つメソッドを渡して、子でも使えるようにしています。
今回は簡素ですが、数が多いformを作るときにはuseFormContextを使うと良いでしょう。
手元と動かしたい人のためのコマンドメモ
npx create-react-app sample-react-hook-form --template typescript
cd sample-react-hook-form
npm install react-hook-form zod @hookform/resolvers
npm start
僕の解説が端的すぎて、もしかするとコードの意味がわかりづらいかもしれませんが、本記事のタイトルの範囲で解説しました。
より詳しく学びたいひと向けの参考文献
react公式
useContext – React
useFormContext内部でuseContextが使われているようです
react-hook-form/src/useFormContext.tsx
デザインパターン
Provider Pattern
最後に
最後まで目を通していただきありがとうございます。
殺傷能力をデバフしたまさかりや、コメント大歓迎です。
よかったらいいね、ストック、拡散もお願いします。
次の人へのバトン
明日は、@g-ssさんの記事です。よろしくお願いします。