>
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の使い方を習得したら、コードを書き直します。