概要
React Hook Form の register でファイル入力(<input type="file">)を管理し、
Zod でファイルサイズや拡張子をバリデーションする。
z.instanceof(FileList) を使うことで FileList を型安全に扱える。
インストール
npm install react-hook-form zod @hookform/resolvers
実装例
// src/components/FileUploadForm.tsx
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useEffect, useState } from "react";
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
const ACCEPTED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp"];
const schema = z.object({
file: z
.instanceof(FileList)
.refine((files) => files.length > 0, "ファイルを選択してください")
.refine(
(files) => files[0].size <= MAX_FILE_SIZE,
"ファイルサイズは 5MB 以下にしてください"
)
.refine(
(files) => ACCEPTED_IMAGE_TYPES.includes(files[0].type),
"JPEG / PNG / WebP のみアップロードできます"
),
});
type FormValues = z.infer<typeof schema>;
export function FileUploadForm() {
const [preview, setPreview] = useState<string | null>(null);
const {
register,
handleSubmit,
watch,
formState: { errors, isSubmitting },
} = useForm<FormValues>({
resolver: zodResolver(schema),
});
const fileList = watch("file");
useEffect(() => {
if (!fileList || fileList.length === 0) return;
const url = URL.createObjectURL(fileList[0]);
setPreview(url);
// コンポーネント unmount 時にオブジェクト URL を解放する
return () => URL.revokeObjectURL(url);
}, [fileList]);
const onSubmit = (data: FormValues) => {
const formData = new FormData();
formData.append("file", data.file[0]);
// TODO: formData をサーバーへ送信する
console.log("送信:", data.file[0].name);
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div>
<label htmlFor="file" className="block text-sm font-medium">
画像ファイル(JPEG / PNG / WebP・5MB 以下)
</label>
<input
id="file"
type="file"
accept="image/jpeg,image/png,image/webp"
className="mt-1 block text-sm"
{...register("file")}
/>
{errors.file && (
<p className="mt-1 text-xs text-red-600">{errors.file.message}</p>
)}
</div>
{preview && (
<img
src={preview}
alt="プレビュー"
className="h-32 w-32 rounded object-cover"
/>
)}
<button
type="submit"
disabled={isSubmitting}
className="rounded bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50"
>
アップロード
</button>
</form>
);
}
ポイント
z.instanceof(FileList)でFileList型を受け取り、.refine()でサイズ・拡張子を検証するwatch("file")で入力の変化を監視し、URL.createObjectURLでプレビューを生成するuseEffectの return でURL.revokeObjectURLを呼び、メモリリークを防ぐ- サーバーへの送信は
FormDataに変換して渡す(Server Actions / API Route いずれにも対応) @hookform/resolversのzodResolverで Zod スキーマを RHF に接続する