react-hook-form-zod-shadcn について
react-hook-form、Zod、shadcn/ui を組み合わせた React フォームの実装ガイドです。リアルタイム検証、スキーマに合う初期値、送信中のフォーム無効化を重視します。
主な機能
- 01react-hook-form の統合
- 02Zod による検証スキーマ定義
- 03shadcn/ui Field パターンの採用
- 04リアルタイム検証と初期値設定
- 05送信中のフィールドとボタンの無効化
活用シーン
- 01検証付き React フォームの構築
- 02入力中のフィードバック実装
- 03送信中の重複操作の防止
- 04統一パターンに沿ったフォーム生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sortweste/ frontend-skills --skill react-hook-form-zod-shadcnインストール後、Claude Code で/react-hook-form-zod-shadcnを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/react-hook-form.mdshadcn-ui.mdzod.md詳細
React Hook Form + Zod + shadcn/ui
クイックスタート
npm install [email protected] [email protected] @hookform/[email protected]
フォームコンポーネントのファイルの先頭には、必ず 'use client' を追加してください。
"use client";
import { useForm } from "react-hook-form";
// ... other imports
export function MyForm() {
// Component logic
}
すべてのフォームで、react-hook-form と連携する shadcn/ui の Field コンポーネントパターンを使用してください。
"use client";
import { useForm } from "react-hook-form";
import {
Field,
FieldDescription,
FieldError,
FieldGroup,
FieldLabel,
} from "@/components/ui/field";
export function MyForm() {
const form = useForm();
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
<FieldGroup>
<Controller
name="title"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel>Title</FieldLabel>
<Input
{...field}
aria-invalid={fieldState.invalid}
placeholder="Login button not working on mobile"
autoComplete="off"
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</FieldGroup>
</form>
);
}
パフォーマンス向上のため、スキーマはコンポーネントの外で定義してください。スキーマの定義には Zod を使用してください。
"use client";
import * as z from "zod";
import { useForm } from "react-hook-form";
const formSchema = z.object({
name: z.string().trim().min(5, { error: "Must be at least 5 characters." }),
description: z.string().trim().min(20, { error: "Must be at least 20 characters." }),
.string()
.trim()
.min(20, { error: "Must be at least 20 characters." }),
});
type FormData = z.infer<typeof formSchema>;
export function MyForm() {
const form = useForm<FormData>();
// ...
}
React の useId フックを使用して、フォームごとに一意の ID を1つだけ生成してください。フィールドごとに個別の ID を生成しないでください。一度だけ生成し、フィールド名と組み合わせて再利用してください。
"use client";
import { useId } from "react";
import { useForm } from "react-hook-form";
export function MyForm() {
const formId = useId(); // Generate once
const form = useForm();
return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
<FieldGroup>
<Controller
name="title"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={`${formId}-title`}>Title</FieldLabel>
<Input
{...field}
id={`${formId}-title`} // Use it with template literals for each field.
aria-invalid={fieldState.invalid}
placeholder="Login button not working on mobile"
autoComplete="off"
disabled={form.formState.isSubmitting}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</FieldGroup>
</form>
);
}
リアルタイムでバリデーション結果をフィードバックするため、useForm フックの設定には必ず mode: "onChange" を追加してください。
const form = useForm<FormData>({
resolver: standardSchemaResolver(formSchema),
mode: "onChange", // Validates on every change
});
スキーマで想定される型に一致する defaultValues を指定してフォームを初期化してください。
const form = useForm<FormData>({
resolver: standardSchemaResolver(formSchema),
mode: "onChange",
defaultValues: {
username: "",
email: "",
},
});
重要:現在発生している問題のため、必ず @hookform/resolvers/standard-schema からインポートしつつ、standard-schema のリゾルバーパターンを使用してください。
"use client";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
const formSchema = z.object({
//...
});
type FormData = z.infer<typeof formSchema>;
export function MyForm() {
const form = useForm<FormData>({
resolver: standardSchemaResolver(formSchema),
mode: "onChange",
});
}
重複送信を防ぎ、UX を向上させるため、form.formState.isSubmitting が true の場合は、送信ボタンとすべてのフォームフィールドを無効にしてください。
return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
<FieldGroup>
<Controller
name="email"
control={form.control}
render={({ field, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={`${formId}-email`}>email</FieldLabel>
<Input
{...field}
id={`${formId}-email`}
aria-invalid={fieldState.invalid}
placeholder="[email protected]"
autoComplete="off"
disabled={form.formState.isSubmitting}
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
</FieldGroup>
<Button type="submit" form={formId} disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting ? "Submitting..." : "Submit"}
</Button>
</form>
);
フォームが有効でない場合は、form.formState.isValid を使用して送信ボタンを無効にしてください。
return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
{/* Form fields */}
<Button
type="submit"
form={formId}
disabled={!form.formState.isValid || form.formState.isSubmitting}
>
{form.formState.isSubmitting ? "Submitting..." : "Submit"}
</Button>
</form>
);
重要:フォームの送信ハンドラーでは、フィールドの値を検証するため、必ず safeParse() を使用してください。検証に失敗した場合は、form.trigger() を呼び出してエラーを表示してください。
"use client";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { standardSchemaResolver } from "@hookform/resolvers/standard-schema";
const formSchema = z.object({
//...
});
type FormData = z.infer<typeof formSchema>;
export function MyForm() {
const form = useForm<FormData>({
resolver: standardSchemaResolver(formSchema),
mode: "onChange",
});
const formId = useId(); // Generate once
const handleSubmit = async (data: FormData) => {
// Validate using safeParse
const result = formSchema.safeParse(data);
if (!result.success) {
// Trigger form validation to show errors
form.trigger();
return;
}
try {
// API call or other submission logic
} catch (error) {
console.error("Submission error:", error);
}
};
return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
{/* Form fields */}
</form>
);
}
送信後にフォームをリセットしてください。
const handleSubmit = async (data: FormData) => {
//... submission logic
form.reset(); // Reset form after successful submission
};
リソース
Zod のリファレンス:references/zod.mdを参照してください。 shadcn/ui のリファレンス:references/shadcn-ui.mdを参照してください。 React Hook Form のリファレンス:references/react-hook-form.mdを参照してください。