Form
react-hook-formと連携したフォームコンポーネント。
インポート
react-hook-form依存のため、専用エントリポイント /form からインポートします(FormはServer Componentsで使えないためrootからは意図的に除外されています)。
'use client'
import { useForm } from 'react-hook-form'
import {
Form,
FormField,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
} from '@caroainc/ui-components/form'
import { Input } from '@caroainc/ui-components/server'
import { Button } from '@caroainc/ui-components/server'Composition
Form
└─ form(ネイティブ要素)
└─ FormField
└─ FormItem
├─ FormLabel?
├─ FormControl
│ └─ Inputなどのフォーム部品(単一子)
├─ FormDescription?
└─ FormMessage?Formの内側には送信を担うネイティブform要素を置き、各フィールドはFormFieldのrender内でFormItemとして構成します。FormControlはSlotのためフォーム部品を1つだけ子にし、Label、Description、MessageをControl内へ入れないでください。
基本的な使い方
interface FormData {
email: string
password: string
}
function LoginForm() {
const form = useForm<FormData>()
const onSubmit = (data: FormData) => {
console.log(data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>メールアドレス</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>パスワード</FormLabel>
<FormControl>
<Input type="password" {...field} />
</FormControl>
<FormDescription>8文字以上で入力してください</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">ログイン</Button>
</form>
</Form>
)
}バリデーション付き
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
const schema = z.object({
email: z.string().email('有効なメールアドレスを入力してください'),
password: z.string().min(8, '8文字以上で入力してください'),
})
const form = useForm({
resolver: zodResolver(schema),
})サブコンポーネント
| コンポーネント | 役割 |
|---|---|
Form | react-hook-formのFormProviderそのもの |
FormField | Controllerのラッパー。nameをcontextで配下に共有する |
useFormField | FormField配下で使う内部フック。id・error・各要素のid(formItemId等)を返す |
FormItem | 1フィールド分のラッパー(React.useId()でid採番) |
FormLabel | ラベル。エラー時にdata-errorが付き赤字になる。htmlForは自動配線される |
FormControl | 実際の入力要素(Input等)をSlot合成し、id・aria-describedby・aria-invalidを自動配線する |
FormDescription | 説明文。idが自動配線されaria-describedbyから参照される |
FormMessage | エラーメッセージ。errorがあればそれを表示し、無ければchildrenを表示。両方無ければ描画されない |
Props (FormField)
| Prop | Type | Default | Description |
|---|---|---|---|
control* | Control | - | react-hook-formのcontrol |
name* | string | - | フィールド名 |
render* | (props) => ReactNode | - | フィールドのレンダリング関数 |
Props (FormItem / FormLabel / FormControl / FormDescription / FormMessage)
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | 追加のCSSクラス(全サブコンポーネント共通) |
children | ReactNode | - | FormMessageの場合、react-hook-formのエラーが無いときのみ表示される |
react-hook-formを使わない単純なフォームレイアウトが必要な場合は、react-hook-form非依存の Field コンポーネント群も利用できます。