3 Ways to Handle Form Submission in NextJS
If you want to head over to the code on Github, Form Management with NextJS.
We all have been using forms in our web applications. Whether it's a simple contact form or a complex form with multiple fields, form handling is an essential part of any web application. In this post, we'll explore 3 different approaches to handle form submissions in NextJS: using Formik and Yup, using React Hook Form, and handling forms without any library.
To keep this post short, we'll use a simple contact form with name, email, password and phone fields. We will also additionally include a checkbox for accepting terms and conditions.
We are using the following versions for this example:
- NextJS -
14.2.15
- ReactJS -
18.0.0
- Formik -
2.2.9
- Yup -
0.3.5
- TailwindCSS -
3.4.1
1. Using Vanilla Form Handling
Vanilla form handling is the simplest way to handle form submissions in NextJS. It involves using the native HTML form elements and handling the form data in the component's state. For this, we will not be using any library.
The form UI in all the examples will be the same as shown below:
Below is the code for the form submission without using any library:
import { useState } from "react";
import FormHeader from "./FormHeader";
import Modal from "./Modal";
export default function WithoutUsingLibrary() {
// This is where we will hold the form data
const [formData, setFormData] = useState({
fullName: "",
email: "",
password: "",
phone: "",
terms: false,
});
const [errors, setErrors] = useState({});
const [showModal, setShowModal] = useState(false);
const [submittedData, setSubmittedData] = useState(null);
const validateForm = () => {
const newErrors = {};
if (!formData.fullName.trim()) {
newErrors.fullName = "Full name is required";
}
if (!formData.email.trim()) {
newErrors.email = "Email is required";
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = "Email is invalid";
}
if (!formData.password) {
newErrors.password = "Password is required";
} else if (formData.password.length < 6) {
newErrors.password = "Password must be at least 6 characters";
}
if (!formData.terms) {
newErrors.terms = "You must accept the terms and conditions";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData((prev) => ({
...prev,
[name]: type === "checkbox" ? checked : value,
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors((prev) => ({
...prev,
[name]: undefined,
}));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (validateForm()) {
try {
// Store the submitted data
setSubmittedData(formData);
setShowModal(true);
// Reset form after successful submission
setFormData({
fullName: "",
email: "",
password: "",
phone: "",
terms: false,
});
} catch (error) {
console.error("Submission error:", error);
}
}
};
return (
<div className="py-8 w-full max-w-md flex items-start justify-center">
<div>
<FormHeader title={"Not Using Any Library"} />
<form className="space-y-4" onSubmit={handleSubmit}>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="fullName">
Full Name
</label>
<input
type="text"
id="fullName"
name="fullName"
value={formData.fullName}
onChange={handleChange}
className={`w-full px-3 py-2 border ${
errors.fullName
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.fullName && (
<p className="text-red-500 text-xs mt-1">{errors.fullName}</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="email">
Email
</label>
<input
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
className={`w-full px-3 py-2 border ${
errors.email
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.email && (
<p className="text-red-500 text-xs mt-1">{errors.email}</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="password">
Password
</label>
<input
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
className={`w-full px-3 py-2 border ${
errors.password
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.password && (
<p className="text-red-500 text-xs mt-1">{errors.password}</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="phone">
Phone Number
</label>
<input
type="tel"
id="phone"
name="phone"
value={formData.phone}
onChange={handleChange}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="terms"
name="terms"
checked={formData.terms}
onChange={handleChange}
className={`rounded border-gray-300 dark:border-gray-700 text-blue-500 focus:ring-blue-500 ${
errors.terms ? "border-red-500" : ""
}`}
/>
<label
className="text-sm text-gray-600 dark:text-gray-400"
htmlFor="terms"
>
I agree to the Terms and Conditions
</label>
</div>
{errors.terms && (
<p className="text-red-500 text-xs">{errors.terms}</p>
)}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
Submit Form
</button>
</form>
</div>
{showModal && submittedData && <Modal data={submittedData} />}
</div>
);
}
2. Using Formik and Yup
Formik is a popular library for handling forms in React. It provides a set of tools for managing form state, validation, and submission. Yup is a schema builder for runtime value parsing. It allows you to define validation rules for your form fields and provides a way to validate the form data before submission.
For this method of form submission, we will need to install the formik
and yup
libraries.
Below is the code for the form submission using Formik and Yup:
import { ErrorMessage, Field, Form, Formik } from "formik";
import { useState } from "react";
import * as Yup from "yup";
import FormHeader from "./FormHeader";
import Modal from "./Modal";
// Validation schema using Yup
const validationSchema = Yup.object().shape({
fullName: Yup.string().required("Full name is required"),
email: Yup.string().email("Email is invalid").required("Email is required"),
password: Yup.string()
.min(6, "Password must be at least 6 characters")
.required("Password is required"),
phone: Yup.string(),
tos: Yup.boolean().oneOf([true], "You must accept the terms and conditions"),
});
export default function UsingFormikYupLibrary() {
const [showModal, setShowModal] = useState(false);
const [submittedData, setSubmittedData] = useState(null);
return (
<div className="p-8 w-full max-w-md flex items-start justify-center">
<div>
<FormHeader title={"Using Formik & Yup"} />
<Formik
className="space-y-6"
initialValues={{
fullName: "",
email: "",
password: "",
phone: "",
tos: false,
}}
validationSchema={validationSchema}
onSubmit={(values) => {
setSubmittedData(values);
setShowModal(true);
}}
>
{({ handleChange, handleBlur }) => (
<Form className="space-y-4">
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="fullName">
Full Name
</label>
<Field
type="text"
id="fullName"
name="fullName"
className="w-full px-3 py-2 border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
<ErrorMessage
name="fullName"
component="p"
className="text-red-500 text-xs mt-1"
/>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="email">
Email
</label>
<Field
type="email"
id="email"
name="email"
className="w-full px-3 py-2 border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
<ErrorMessage
name="email"
component="p"
className="text-red-500 text-xs mt-1"
/>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="password">
Password
</label>
<Field
type="password"
id="password"
name="password"
className="w-full px-3 py-2 border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
<ErrorMessage
name="password"
component="p"
className="text-red-500 text-xs mt-1"
/>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="phone">
Phone Number
</label>
<Field
type="tel"
id="phone"
name="phone"
className="w-full px-3 py-2 border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
</div>
<div className="flex items-center gap-2">
<Field
type="checkbox"
id="tos"
name="tos"
className="rounded border-gray-300 text-blue-500 focus:ring-blue-500"
/>
<label
className="text-sm text-gray-600 dark:text-gray-400"
htmlFor="tos"
>
I agree to the Terms and Conditions
</label>
</div>
<ErrorMessage
name="tos"
component="p"
className="text-red-500 text-xs"
/>
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
Submit Form
</button>
</Form>
)}
</Formik>
</div>
{showModal && submittedData && <Modal data={submittedData} />}
</div>
);
}
3. Using React Hook Form
The third method for submission of a form is using React Hook Form. For this you are going to need to install the react-hook-form
library.
"use client";
import { useCallback, useState } from "react";
import { useForm } from "react-hook-form";
import FormHeader from "./FormHeader";
import Modal from "./Modal";
export default function UsingReactFormsLibrary() {
const {
register,
handleSubmit,
getValues,
formState: { errors },
} = useForm();
const [showModal, setShowModal] = useState(false);
const [submittedData, setSubmittedData] = useState(null);
const onSubmit = useCallback(() => {
console.log(getValues());
setSubmittedData(getValues());
setShowModal(true);
}, [getValues]);
return (
<div className="p-8 w-full max-w-md flex items-start justify-center">
<div>
<FormHeader title={"Using React Forms"} />
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="fullName">
Fullname
</label>
<input
type="text"
id="fullName"
name="fullName"
{...register("fullName", {
required: "Fullname field is required",
})}
className={`w-full px-3 py-2 border ${
errors.fullName
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.fullName && (
<p className="text-red-500 text-xs mt-1">
{errors.fullName?.message}
</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="email">
Email
</label>
<input
type="email"
id="email"
name="email"
{...register("email", { required: "Email is required" })}
className={`w-full px-3 py-2 border ${
errors.email
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.email && (
<p className="text-red-500 text-xs mt-1">
{errors.email?.message}
</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="password">
Password
</label>
<input
type="password"
id="password"
name="password"
{...register("password", { required: "Password is required" })}
className={`w-full px-3 py-2 border ${
errors.password
? "border-red-500"
: "border-gray-300 dark:border-gray-700"
} rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800`}
/>
{errors.password && (
<p className="text-red-500 text-xs mt-1">
{errors.password?.message}
</p>
)}
</div>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="phone">
Phone Number
</label>
<input
type="tel"
id="phone"
name="phone"
{...register("phone", { required: "this field is required" })}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 dark:bg-gray-800"
/>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
{...register("termsReactForms", {
required: "You must accept the terms and conditions",
})}
className={`rounded border-gray-300 dark:border-gray-700 text-blue-500 focus:ring-blue-500 ${
errors.termsReactForms ? "border-red-500" : ""
}`}
/>
<label
htmlFor="termsReactForms"
className="text-sm text-gray-600 dark:text-gray-400"
>
I agree to the Terms and Conditions
</label>
</div>
{errors.termsReactForms && (
<p className="text-red-500 text-xs">
{errors.termsReactForms?.message}
</p>
)}
<button
type="submit"
className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
Submit Form
</button>
</form>
</div>
{showModal && submittedData && <Modal data={submittedData} />}
</div>
);
}
When the form is submitted, we are showing the captured data in a modal for brevity. If you want a quick look at the code, you can head over to the Modal component.