Building a contact form in nextjs?
Building a contact form in nextjs?

Nextjs

Building a contact form in nextjs 14?

Building next.js contact form from scratch.

--

For every website, such as an agency, company, etc., is most important is a contact form. The contact form helps customers reach out to you.

If customers or users have a problem or query related to your services or product, they can contact you.

Building the contact form is not easy for a front-end developer. A lot of agencies and companies use databases only for contact forms.

It is not a good approach, but now there are good alternatives available for building and handling forms.

One of them is a submit json; it is a form backend that helps to build a contact form in your website without any database. You can make a contact form on your website in less than ten minutes.

In this tutorial, we use nextjs 14, tailwind CSS, React hook form, and Shadcn UI to build the form UI.

All the code is available on GitHub.

Demo

Demo submitjson Example
Demo Example

Requirements

Before starting this article, You know about the following libraries:

  1. Shadcn UI
  2. React hook form
  3. Zod

Steps

  1. Create nextjs app
  2. Install additional package
    Install Shadcn UI
    Add shadcn component
    Install Zod and React Hook Form
    Validator package (Optional)
    Install submitjson package
  3. Build contact form UI
  4. Save the form data
    API Built
    Submit data

Create nextjs app

The first step is to create a fresh new nextjs application using the create next app. If you already have a project, then skip this section.

➜  medium pnpm create next-app@latest submitjson     
.../share/pnpm/store/v3/tmp/dlx-226259 | +1 +
.../share/pnpm/store/v3/tmp/dlx-226259 | Progress: resolved 1, reused 1, downloaded 0, added 1, done
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /home/officialrajdeepsingh/medium/submitjson.

Using pnpm.

Initializing project with template: app-tw


Installing dependencies:
- react
- react-dom
- next

Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- postcss
- tailwindcss
- eslint
- eslint-config-next

WARN 3 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6, rimraf@3.0.2
Packages: +349
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 357, reused 349, downloaded 0, added 349, done

dependencies:
+ next 14.2.3
+ react 18.3.1
+ react-dom 18.3.1

devDependencies:
+ @types/node 20.12.12
+ @types/react 18.3.3
+ @types/react-dom 18.3.0
+ eslint 8.57.0 (9.3.0 is available)
+ eslint-config-next 14.2.3
+ postcss 8.4.38
+ tailwindcss 3.4.3
+ typescript 5.4.5

Done in 15.8s
Initialized a git repository.

Success! Created submitjson at /home/officialrajdeepsingh/medium/submitjson

Install additional package

To build a contact form, you need to install additional dependencies:

  1. Install Shadcn UI
  2. Add shadcn component
  3. Install Zod and React Hook Form
  4. Validator and Sonner package (Optional)
  5. Install submitjson package

Install Shadcn UI

➜  submitjson git:(main) ✗ pnpm dlx shadcn-ui@latest init

WARN GET https://registry.npmjs.org/queue-microtask error (ECONNRESET). Will retry in 10 seconds. 2 retries left.
WARN GET https://registry.npmjs.org/string_decoder error (ECONNRESET). Will retry in 10 seconds. 2 retries left.
Packages: +175
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 175, reused 146, downloaded 29, added 175, done
✔ Which style would you like to use? › New York
✔ Which color would you like to use as base color? › Slate
✔ Would you like to use CSS variables for colors? … no / yes

✔ Writing components.json...
✔ Initializing project...
✔ Installing dependencies...

Success! Project initialization completed. You may now add components.

Add shadcn component

To build contacts from UI, we need to add some components from shadcn UI such as label, input, button, textarea, select, and sonner.

➜  submitjson git:(main) ✗ pnpm dlx shadcn-ui@latest add label input button textarea select form sonner

Packages: +175
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 175, reused 175, downloaded 0, added 175, done
✔ Done.

Install Zod and React Hook Form

To validate the form input, we need to install Zod and React Hook From the library.

➜  submitjson git:(main) ✗ pnpm add zod react-hook-form           
WARN 3 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6, rimraf@3.0.2
Already up to date
Progress: resolved 405, reused 397, downloaded 0, added 0, done
Done in 7.3s

Validator and Sonner package (Optional)

To verify whether the phone number is user-entered is validated or not, we install the validator library, which is an optional library you can install or not. It is your choice.

The Sonner package is a toast component built for React and shows notifications on the screen. In our case, we show a success or error notification when the form is submitted.

➜  submitjson git:(main) ✗ pnpm add validator @types/validator sonner                   
Packages: +3
+
Progress: resolved 406, reused 397, downloaded 1, added 1, done

dependencies:
+ validator 13.12.0

Done in 4.5s

Install submitjson package

The most important is to install the submitjson package.

➜  submitjson git:(main) ✗ pnpm add submitjson         
WARN 3 deprecated subdependencies found: glob@7.2.3, inflight@1.0.6, rimraf@3.0.2
Packages: +3
+++
Progress: resolved 409, reused 398, downloaded 3, added 3, done

dependencies:
+ submitjson 0.8.1

Done in 6.9s

Build contact form UI

First, check out what looks like the contact form UI.

Contact form UI
Contact form UI

First, build schemas for form using the Zod library. The form schema helps to validate the form field.

We have eight forms filed, such as first name, last name, username, email, message, phone number, and services.

Our schema looks like this: if you learn more about the Zod library, you will watch tutorials on YouTube or read documentation.

// components/Form/schema.ts

import { z } from "zod";
import {isMobilePhone} from "validator";

export const formSchema = z.object({
username: z.string().min(2, {
message: "Username must be at least 2 characters.",
}).max(12,{
message :"Username does not extend more than 12 characters."
}),

firstName: z.string().trim().min(2,{
message:"First name must be at least 2 characters."
}),

lastName: z.string().min(2, {
message: "last name must be at least 2 characters.",
}),

email: z.string().email({
message: "Enter the email"
}).trim().toLowerCase(),

phoneNumber: z.string({
message: "Enter mobile number"
}).min(10,{
message: "Phone number is short."
}).max(10,{
message: "Phone number is big."
}).refine((phone) => isMobilePhone(phone),{
message: "Enter vaild mobile number"
}),

message: z.string({message: "Enter message"}).min(12, {
message: "message must be at least 20 characters.",
}),

service: z.enum(["web-design", "web-development","seo","social-media","branding"],{
message: "Select a service"
})

})

Note
I used the validator library for mobile number verification, but it didn’t work for me. You can try other libraries, is-phone-number, etc., with Zod schema.

The next step we build UI using React hook form, Shadcn UI, and Zod. We use the useForm hook from the react-hook-form library to validate the form data.

// components/Form/form.tsx 

"use client"

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { SelectValue, SelectTrigger, SelectItem, SelectContent, Select } from "@/components/ui/select";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input"
import { formSchema } from "./schema"
import { Textarea } from "@/components/ui/textarea";
import { toast } from "sonner"

export function ContactForm() {

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
username: "",
firstName: "",
lastName: "",
email: "",
phoneNumber: "",
message: "",
service: "seo"
},
})

return (
<Form {...form}>

<form className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">

<FormField
control={form.control}
name="firstName"
render={({ field }) => (
<FormItem className="space-y-2">
<FormLabel>First Name</FormLabel>
<FormControl>
<Input autoComplete="cc-given-name" placeholder="Rajdeep" {...field} />
</FormControl>
<FormDescription>
Enter the First name.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="lastName"
render={({ field }) => (
<FormItem className="space-y-2">
<FormLabel>Last Name</FormLabel>
<FormControl>
<Input type="text" autoComplete="cc-family-name" placeholder="Singh" {...field} />
</FormControl>
<FormDescription>
Enter the Last name.
</FormDescription>
<FormMessage />
</FormItem>
)} />

</div>

<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" autoComplete="email" placeholder="me@example.com" {...field} />
</FormControl>
<FormDescription>
Enter your email.
</FormDescription>
<FormMessage />
</FormItem>
)} />

<FormField
control={form.control}
name="username"
rules={{ required: true }}
render={({ field }) => (
<FormItem>
<FormLabel>Username</FormLabel>
<FormControl>
<Input placeholder="official_r_deep" {...field} />
</FormControl>
<FormDescription>
Enter your user name.
</FormDescription>
<FormMessage />
</FormItem>)} />

<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea id="message" placeholder="Your message..." rows={4} {...field} />
</FormControl>
<FormDescription>
Enter your message.
</FormDescription>
<FormMessage />
</FormItem>)} />

<FormField
control={form.control}
name="phoneNumber"
render={({ field }) => (
<FormItem>
<FormLabel>Phone Number</FormLabel>
<FormControl>
<Input type="tel" autoComplete="tel" placeholder="+1 (555) 555-5555" {...field} />
</FormControl>
<FormDescription>
Enter your phone number.
</FormDescription>
<FormMessage />
</FormItem>)} />


<FormField
control={form.control}
name="service"
render={({ field }) => (
<FormItem className="space-y-2">
<FormLabel>Services</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<SelectTrigger>
<SelectValue {...field} placeholder="Select Services" />
</SelectTrigger>
<SelectContent>
<SelectItem value="web-design">Web Design</SelectItem>
<SelectItem value="web-development">Web Development</SelectItem>
<SelectItem value="seo">SEO</SelectItem>
<SelectItem value="branding">Branding</SelectItem>
<SelectItem value="social-media">Social Media</SelectItem>
</SelectContent>
</Select>

<FormDescription>
Select your service.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button className="w-full mt-12" type="submit"> Submit </Button>
</form>
</Form>
)
}

Now, I import the form component on the home page.

// app/page.tsx

import { ContactForm } from "@/components/Form/form"

export default function Page() {
return (
<div className="mt-16 max-w-2xl mx-auto p-6 sm:p-8 bg-white rounded-lg shadow-lg dark:bg-gray-900">
<div className="space-y-6">
<div className="space-y-2">
<h2 className="text-2xl font-bold tracking-tight text-gray-900 dark:text-white">Get in Touch</h2>
<p className="text-gray-500 dark:text-gray-400">Fill out the form below to send us a message.</p>
</div>
<ContactForm />
</div>
</div>
)
}

Save the form data

The Most important step is now starting; now, we save the data whenever the user fills the form in your nextjs application.

To save the data, we use the submit json as a backend for our form. For better understanding, this section is divided into two parts.

  1. API built
  2. Submit data

API Built

To build API, we need two environment variables the first is API keys, and the second is an endpoint.

To get the environment variables, go to the submitjson website dashboard, select the project if you are a first-time user to create your first project, and otherwise select your existing project.

List of projects.
List of projects.

Now, Go to your project and the go-to instructions section, and you can find your API key and endpoint. Copy both the environment variables and paste them into your .env.local file.

Environment variables
Environment variables

Now, we pass both environment variables in the submitjson function. With the help of the submitjson function, you can submit form data. Now, your API.

Check out the API example below here.

// app/api/contact/route.ts

import { NextRequest, NextResponse } from 'next/server';
import SubmitJSON from 'submitjson';

const sj = new SubmitJSON({
apiKey: process.env.SUBMIT_JSON_API_KEY as string,
endpoint: process.env.ENDPOINT
})

export async function POST(request: NextRequest) {

const body = await request.json();

const response = await sj.submit(body);

if (response) {
return NextResponse.json({ message: `Form is submit`, response }, { status: 200 })
}
else {
return NextResponse.json({ message: `something wrong`, response }, { status: 400 })
}

}

Submit data

Now, we will call the API which we create to submit from data on submitjson.

We can call the API on form submit. Whenever the user fills out the form and clicks the submit button. First, Zod checks the validation. If everything is fine, it calls the submit form function; otherwise, it shows the error.

Checkout the code below:

"use client"

export function ContactForm() {

// ...

async function onSubmit(values: z.infer<typeof formSchema>) {

await fetch('http://localhost:3000/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(values),
}).then(async (response) => {
if (response.status !== 200 && !response.ok) throw new Error();
toast.success("Your form is submit successfully.")
})
.catch(() => {
toast.error('Something wrong error')
})
}

// ...

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
</form>
</Form>
)
}

If the form data is successfully submitted, we show a successful message on the screen with a sonner library.

Note

The nextjs action doesn’t support by React Hook Form library. So that way, I chose the submit method to submit form data through API.

In the future, If the React Hook Form library supports form actions, then use form actions. Do not need to create API. Just call the submitjson function directly from the action.

FAQ

How to view form data on submit json if somebody submits a form?

You can view all the form submit data, go to the submitjson website and click the activity link, and you can see all submitted forms.

All the submitted emails list
All the submitted emails list

Conclusion

The submitjson, not single one there other service provide available on the internet. You can check out those as well.

Before using submitjson, first, check out the pricing page as well. The submitjson is very good for form backend, especially for small companies or freelancers.

You can integrate submitjson with other tools such as Slack, Zapier, webhook, etc.

To learn more about frontend developers, reactjs, nextjs, and Linux stuff, follow the frontend web publication on Medium and other updates. You can also follow me on Twitter (X) and Medium.

--

--

Rajdeep Singh
FrontEnd web

JavaScript | TypeScript | Reactjs | Nextjs | Rust | Biotechnology | Bioinformatic | Frontend Developer | Author | https://linktr.ee/officialrajdeepsingh