0

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>
    )
}
2

1 Answer 1

2

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.')
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.