javascriptreactjsreact-hook-formzod

Issues connecting React hook form validated with zod, with Email Js


I am having issues connecting Email js with my React Hook Form, there are various issues like it wont submit on click and can't figure out a way to use ref attribute with the <form> tag, as it's mentioned in the official documentation.

Here's my code

'use client'

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import Image from "next/image";
import { useState } from "react";
import emailjs from '@emailjs/browser';

import contact from "@/assets/images/contact.png";


const UserValidation = z.object({
    name: z.string().min(3).max(50),
    email: z.string().email(),
    message: z.string().min(10).max(500),
});

interface Props {
    user: {
        name: string;
        email: string;
        message: string;
    };
}

const Contact = () => {
    const form = useForm({
        resolver: zodResolver(UserValidation),
    });

    const onSubmit = () => (data: z.infer<typeof UserValidation>) => {

        emailjs.sendForm('__service_key', '__template__', JSON.stringify(data), '__API__')
            .then((result) => {
                console.log(result.text);
            }, (error) => {
                console.log(error.text);
            });

        form.reset();
    }

    return (


        <div className="my-20">
            <div className="my-12">

                <h1 className="text-3xl md:text-5xl text-center font-serif font-bold">
                    Contact <span className="text-primary">Us</span>
                </h1>

                <h1 className="text-base text-center font-sans font-thin mt-4">
                    We would love to hear from you
                </h1>

            </div>

            <div className="md:grid md:grid-cols-2 mb-12">


                <div className="m-12 md:m-0 md:ml-36 ">



                    <Form {...form} >
                        <form onSubmit={form.handleSubmit(onSubmit)}>
                            <FormField control={form.control} name="name" render={({ field }) => (
                                <FormItem className='flex flex-col gap-1 w-full mt-4'>
                                    <FormLabel className='text-light-1 font-sans font-sm'>
                                        Name
                                    </FormLabel >
                                    <FormControl className='flex-1 text-base-semibold text-secondary'>
                                        <Input type='text' className='border-primary account-form_input no focus' {...field} />
                                    </FormControl>
                                </FormItem>)}
                            />
                            <FormField control={form.control} name="email" render={({ field }) => (
                                <FormItem className='flex flex-col gap-1 w-full mt-4'>
                                    <FormLabel className='text-light-1 font-sans font-sm'>
                                        Email
                                    </FormLabel >
                                    <FormControl className='flex-1 text-base-semibold text-secondary'>
                                        <Input type='email' className='border-primary account-form_input no focus' {...field} />
                                    </FormControl>
                                </FormItem>)}
                            />

                            <FormField
                                control={form.control}
                                name="message"
                                render={({ field }) => (
                                    <FormItem className='flex flex-col gap-1 mt-4 w-full'>
                                        <FormLabel className='text-light-1 font-sans font-sm'>
                                            Message
                                        </FormLabel>
                                        <FormControl className='flex-1 text-base-semibold text-secondary'>
                                            <Textarea rows={10} className='border-primary account-form_input no focus' {...field} />
                                        </FormControl>
                                    </FormItem>
                                )}
                            />

                            <div className="flex">
                                <Button type="submit" className='bg-primary mt-4 flex-1'>Submit</Button>
                            </div>

                        </form>
                    </Form>
                </div>

                <div className="m-12 md:m-0 md:ml-28">
                    <Image src={contact} alt="contact" className="rounded-lg shadow-custom" height={470} width={470} />
                </div>
            </div>
        </div>
    )

}

export default Contact;

The official documentation mentions it like:

import React, { useRef } from 'react';
import emailjs from '@emailjs/browser';

export const ContactUs = () => {
  const form = useRef();

  const sendEmail = (e) => {
    e.preventDefault();

    emailjs.sendForm('YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', form.current, 'YOUR_PUBLIC_KEY')
      .then((result) => {
          console.log(result.text);
      }, (error) => {
          console.log(error.text);
      });
  };

  return (
    <form ref={form} onSubmit={sendEmail}>
      <label>Name</label>
      <input type="text" name="user_name" />
      <label>Email</label>
      <input type="email" name="user_email" />
      <label>Message</label>
      <textarea name="message" />
      <input type="submit" value="Send" />
    </form>
  );
};

you can also visit my github for the whole code: https://github.com/dipesh2508/health-optima


Solution

  • First, Your code has some errors

    // ❌ Here you return a function that returns another function
    const onSubmit = () => (data: z.infer<typeof UserValidation>) => {
    // ✅ It should be
    const onSubmit = (data: z.infer<typeof UserValidation>) => {
    

    It looks like emailJs take over getting data from the input fields. It accepts the dom object.

    But form.handleSubmit() passes the values of the form, not the form object. You'll have to skip the react-hook-form submission and submit it manually.

    1. Remove handleSubmit from your form:
    <form onSubmit={onSubmit}>
    
    1. This function type will be changed
    const onSubmit = (event: FormEvent<HTMLFormElement>) => {
    
    1. Setup the custom function to validate the form
    const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
      event.preventDefault();
      
      // Validate the form
      const isValid = await form.trigger();
    
      if (!isValid) {
        // there is some validation errors
        return
      }
    
      emailjs.sendForm('__service_key', '__template__', event.target, '__API__')
     .then((result) => {
       console.log(result.text);
     }, (error) => {
       console.log(error.text);
     });
     form.reset();
    }
    

    Another way for doing the same is creating a ref for the form

    const formRef = useRef<HTMLFormElement>(null)
    // ...
    
    const onSubmit => (data: z.infer<typeof UserValidation>) => {
      emailjs.sendForm('__service_key', '__template__', formRef.current, '__API__')
     .then((result) => {
       console.log(result.text);
     }, (error) => {
       console.log(error.text);
     });
     form.reset();
    }
    // ...
    <form onSubmit={form.handleSubmit(onSubmit)} ref={formRef}>