Joyal
Joyal

Reputation: 2691

React hook form and zod - make file input optional with validations

I have a form with 3 fields, textbox, textarea and a file input. Textbox and textarea are required fields and file input is optional.

I am using react-hook-form, zod and zodResolver.

I added a scheme and set file input as optional in the schema, but when ever I submit the form without selecting a file, its showing error.

I want to show the validations for file input only if a file is selected (ie file input is optional)

'use client'

import { z } from "zod";
import { useForm } from 'react-hook-form'
import { zodResolver } from "@hookform/resolvers/zod";

export default function Form() {

    const MAX_FILE_SIZE = 2000000
    const ACCEPTED_IMAGE_TYPES = [
        'image/jpeg',
        'image/jpg',
        'image/png',
        'image/webp',
    ]

    const imageSchema = z.any()
        // To not allow empty files
        .refine((files) => files?.length >= 1, { message: 'Photo is required.' })
        // To not allow files other than images
        .refine((files) => ACCEPTED_IMAGE_TYPES.includes(files?.[0]?.type), {
            message: '.jpg, .jpeg, .png and .webp files are accepted.',
        })
        // To not allow files larger than 5MB
        .refine((files) => files?.[0]?.size <= MAX_FILE_SIZE, {
            message: `Max file size is 2MB.`,
        }).optional()

    const baseSchema = z.object({
        title: z.string().min(1, 'Title required').max(60, 'Maximum 60 characters.'),
        message: z.string().min(1, 'Message required.').max(120, 'Maximum 120 characters.'),
        image: imageSchema,
    });


    type FormValues = z.infer<typeof baseSchema>;
    const { register, handleSubmit, control, watch, reset, formState: { errors, isSubmitting } } = useForm<FormValues>({
        resolver: zodResolver(baseSchema),
    });


    //submit form
    const onSubmit = async (data: any) => {
        console.log(data);
    }

    return (
        <div className="mx-auto max-w-md container">
            <form onSubmit={handleSubmit(onSubmit)}>
                <div className="py-8 p-10 bg-slate-100 border-slate-200 rounded-xl border-2">

                    <div className="mb-6">
                        <label className="text-gray-700 font-bold" htmlFor="name">Title</label>
                        <input type="text" {...register('title')} className="w-full border border-gray-300 py-2 pl-3 rounded mt-2 outline-none" />
                        {errors.title && <div className="text-red-500">{errors.title?.message}</div>}
                    </div>

                    <div className="mb-6">
                        <div className="mb-6">
                            <label className="text-gray-700 font-bold" htmlFor="name">Message</label>
                            <textarea rows={4} {...register('message')} className="w-full border border-gray-300 py-2 pl-3 rounded mt-2 outline-none" />
                            {(errors as any).message && <div className="text-red-500">{(errors as any).message?.message}</div>}
                        </div>
                    </div>

                    <div className="mb-6">
                        <label className="text-gray-700 font-bold" htmlFor="name">Photo <span className="text-sm font-normal text-gray-700"> (optional)</span> </label>
                        <input type="file" {...register('image')} className="w-full border border-gray-300 py-2 pl-3 rounded mt-2 outline-none" />
                        {errors.image && <div className="text-red-500">{errors.image?.message?.toString()}</div>}
                    </div>

                    <button disabled={isSubmitting} type="submit" className="disabled:opacity-50 w-full mt-6 text-indigo-50 font-bold bg-indigo-600 py-3 rounded-md hover:bg-indigo-500"><span> Submit</span></button>
                </div>
            </form>
        </div>
    )
}

Upvotes: 0

Views: 5082

Answers (1)

Joyal
Joyal

Reputation: 2691

The issue is in the imageSchema. I updated the image schema and its working as expected

const imageSchema = z.any().optional()
.refine(file => file.length == 1 ? ACCEPTED_IMAGE_TYPES.includes(file?.[0]?.type) ? true : false : true, 'Invalid file. choose either JPEG or PNG image')
.refine(file => file.length == 1 ? file[0]?.size <= MAX_FILE_SIZE ? true : false : true, 'Max file size allowed is 8MB.')

Upvotes: 2

Related Questions