>
Next.js + React Hook Form + Zod で複数の画像ファイルをアップロードする方法について説明します。フォームに入力されたデータをFormData型の変数に代入し、それをSeaver Actionの関数に渡すところまでを説明しています。なお、Next.js初心者による自分用のメモのため、参考程度にしてください。
"use client";
import React from "react"
import { useForm, SubmitHandler } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"
import { Label } from "../ui/label";
import { Input } from "../ui/input"
import { Button } from "../ui/button"
import { createImageWithFormData } from "@/lib/image";
const FormSchema = z.object({
title: z.string()
.max(100, {
message: "100文字以下にしてください。"
}),
caption: z.string()
.max(100, {
message: "100文字以下にしてください。"
}),
file: z.custom<FileList>()
.refine(value => value.length > 0, "ファイルを選択してください。"),
});
type FormFields = z.infer<typeof FormSchema>;
const ImageForm = () => {
const {
register,
handleSubmit,
setError,
formState: { errors, isSubmitting },
} = useForm<FormFields>({
defaultValues: {
title: "",
caption: "",
},
resolver: zodResolver(FormSchema),
});
const onSubmit: SubmitHandler<FormFields> = async (values) => {
try {
const formData = new FormData();
formData.set("title", values.title);
formData.set("caption", values.caption);
Array.from(values.file).forEach((file, index) => {
formData.append("files", file);
});
const result = await createImageWithFormData(formData);
} catch (error) {
setError("root", {
message: "エラーが発生しました。",
})
}
}
return (
<form
className="flex flex-col gap-2"
onSubmit={handleSubmit(onSubmit)}>
<div>
<Label htmlFor="title">Title</Label>
<Input type="text" {...register("title")} />
{errors.title && (
<div className="error">{errors.title.message}</div>
)}
</div>
<div>
<Label htmlFor="caption">Caption</Label>
<Input type="text" {...register("caption")} />
{errors.caption && (
<div className="error">{errors.caption.message}</div>
)}
</div>
<div>
<Input
type="file"
accept="image/*"
multiple
{...register("file")}
/>
{errors.file && (
<div className="error">{errors.file.message}</div>
)}
</div>
<div>
<Button
disabled={isSubmitting}
type="submit"
>{isSubmitting ? "送信中..." : "送信"}</Button>
{errors.root && <div className="error">{errors.root.message}</div>}
</div>
</form>
);
}
export default ImageForm;
fileは、forEachで回してformDataにappendしています。
export async function createImageWithFormData(formData: FormData) {
const title: string = formData.get("title");
const caption: string = formData.get("caption");
const files: File[] = formData.getAll("files") as File[];
console.log("title", title);
console.log("caption", caption);
console.log("files", files);
// アップロードする処理
}
filesは、formData.get()ではなく、formData.getAll()で取得します。
shadcn/uiも使用していますが、shadcn/uiのFormの書き方がよくわかっていないため、Formは使用していません。Formの使い方を習得したら、コードを書き直します。