With any luck, there are enough frontend buzzwords in that title to make this an interesting post!
In this article, I will take you through the steps involved in creating a basic contact form that has been jazzed up with a few libraries that take away some of the pain during development, (and add some of their own challenges).
Breakdown
As the title suggests, we'll be using the following tools:
- NextJS v14.
- EmailJS
- Zod
-
ShadCN UI (under the hood, this is using
react-hook-form
) - TypeScript
Assumptions
This article assumes you have already configured your application to use NextJS - with Typescript, TailwindCSS, Zod. If not, the links above all have docs on how to install and configure these tools
Additionally, you'll need an account with EmailJS. The fun part is that EmailJS offers a free teir version for their services so there's no reason not to!
Let's Get Started
Using your favourite package manager, let's get the following packages installed. In my case, I am using npm
for simplicity.
npm install @emailjs/browser react-hook-form zod @hookform/resolvers
If you haven't already configured ShadCN UI, check out their docs here for NextJS applications. Get started with ShadCN.
Create the Contact Form component
From here it's more straight-forward as we'll use a lot of ShadCN and EmailJS out of the box code, with some minor tweaks to let them work together.
Install your ShadCN components
npx shadcn-ui@latest add form button input toast textarea
Create a Contact.tsx
file in your application
Organise your imports
- Gotcha, check the filepath for the ShadCN components matches your configuration.
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import React, { useRef } from "react";
import { z } from "zod";
import emailjs from "@emailjs/browser";
import { Button } from "~/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { useToast } from "~/components/ui/use-toast"; //optional
import { Textarea } from "~/components/ui/textarea";
Create the form schema with Zod
In most contact forms you'll want the persons name, email address, and their message.
//...imports
const FormSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}),
email: z.string().email("Invalid email address.").min(2, {
message: "Email must be at least 2 characters.",
}),
message: z.string().min(10, {
message: "Message should be at least 10 characters.",
}),
});
Create a reusable component using export
On your site, you want to make it as easy as possible for people to get in contact with you, by exporting the component you can import it in various places throughout your website.
Gotcha: Make sure you pass in your own valid EmailJS Template ID, Service ID, and Public Key. Here I reference them via my .env
file.
In this component, we will need to define the following:
-
const formRef
const form
-
const onSubmit
export const Contact = () => {
// EmailJS needs the `ref` parameter in a form, ShadCN doesn't use
// this by default so we have to import it.
const formRef = useRef<HTMLFormElement | null>(null);
// configure Zod default values for the form
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
username: "",
email: "",
message: "",
},
});
}
// Create the handler that connects to EmailJS.
const onSubmit = (data: z.infer<typeof FormSchema>) => {
if (formRef.current) {
emailjs
.sendForm(
env.NEXT_PUBLIC_EMAILJS_SERVICE_ID,
env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID,
formRef.current,
{
publicKey: env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY,
},
)
.then(
() => {
form.reset(); //clear the fields after submission
},
(error) => {
console.warn("FAILED...", JSON.stringify(error));
},
);
}
};
// ...the ShadCN form will go here.
} //end of Contact component
Create your ShadCN form
Now that all of your functions and variables are declared, you want to create the actual React component that renders onto the screen. For this, we'll use ShadCN's form that is react-hook-form
under the hood.
With this, you'll now have a well formed, semantically correct Form provided by ShadCN, with client side validation, responsive design, and a host of other benefits.
//... functions and variables
return (
<>
<Form {...form}>
<form
ref={formRef} //Required by EmailJS
onSubmit={form.handleSubmit(onSubmit)}
className="w-2/3 space-y-6">
<FormField
name="username"
render={({ field }) => (
<FormItem>
<FormLabel className="text-lg">Name</FormLabel>
<FormControl>
<Input
className="border-primary bg-white"
placeholder="Your Name"
{...field}
/>
</FormControl>
<FormMessage className="text-xs text-red-600" />
</FormItem>
)}/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel className="text-lg">Email</FormLabel>
<FormControl>
<Input
className="border-primary bg-white"
placeholder="Email Address"
{...field}
/>
</FormControl>
<FormMessage className="text-xs text-red-600" />
</FormItem>
)}/>
<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel className="text-lg">Message</FormLabel>
<FormControl>
<Textarea
className="border-primary bg-white"
placeholder="Type your message here."
id="message"
{...field}
/>
</FormControl>
<FormMessage className="text-xs text-red-600" />
</FormItem>
)}/>
<Button
type="submit"
className="text-md text-white hover:bg-secondary">
{/*<PaperPlane />*/}
Send{" "}
</Button>
</form>
</Form>
</>
);
Optional Bonus: Add a Toast
notification
What nicer way to inform your visitors that your email has been sent, or has failed to send, than by adding a neat Toast
notification to your onSubmit handler function.
For this, we'll be using the optional imports above inside the Contact.tsx
component, notably:
npx shadcn-ui@latest add toast
Ensure you have imported:
import { useToast } from "~/components/ui/use-toast";
Inside your Contact
component, desconstruct useToast
const { toast } = useToast();
Inside your onSubmit
handler, add the toast when the email submission is successful, or when it fails, inside the then()
clause.
.then(
() => {
console.info('SUCCESS');
toast({
title: "Email sent.",
description: `Thanks ${data.username}, I'll be in touch.`,
});
form.reset(); //clear the fields after submission
},
(error) => {
toast({
variant: "destructive",
title: "Email failed to send.",
description: `Please contact support if this continues.`,
});
console.warn("FAILED...", JSON.stringify(error));
},
);
Lastly, Toast needs to be referenced inside your layout.tsx
- if you're not seeing the toast notification on your screen, this is likely the cause.
import { Toaster } from "~/components/ui/toaster";
//...
<html lang="en">
<body>
<Toaster /> // Reference toaster
</body>
</html>
Conclusion
And there you have it, with a few tweaks of the default code provided by EmailJS and ShadCN's Form component, you are up and running.
I have provided the entire code here in my GitHub Gist
If you have any thoughts, requests, or questions - let me know. Or, if you would like a complete guide from setting up a new project with all of these tools configured - leave a comment below.
Top comments (0)