Building the Contact Form
Building the Contact Form
File Path: ContactUsForm\src\component\Form.jsx
import React from "react";
import { useState } from "react";
import PhoneInput from "react-phone-input-2";
import "react-phone-input-2/lib/style.css";
import * as Yup from "yup";
import FormSuccess from "./SuccessFormSubmission";
const initialFormData = {
personName: "",
email: "",
phoneNumber: "",
serviceSelected: "",
message: "",
agreement: false,
};
function Form() {
const [formData, setFormData] = useState(initialFormData);
const [errors, setErrors] = useState({});
const [formSubmitted, setFormSubmitted] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const formData_ValidationSchema = Yup.object({
personName: Yup.string()
.min(3, "Name must be at least 3 characters")
.required("Your name is required"),
email: Yup.string()
.matches(
/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/,
"Enter a valid email",
)
.required("Email is required"),
phoneNumber: Yup.string()
.matches(/^\+?[1-9]\d{11,14}$/, "Enter a valid phone number")
.required("Phone number is required"),
serviceSelected: Yup.string().required("Please select a service"),
message: Yup.string()
.min(10, "Message must be at least 10 characters")
.required("Message is required"),
agreement: Yup.boolean().oneOf(
[true],
"You must accept Terms & Conditions",
),
});
const handleChange = async (e) => {
const { name, value, type, checked } = e.target;
const newValue = type === "checkbox" ? checked : value;
setFormData({ ...formData, [name]: newValue });
if (isSubmitted) {
try {
await Yup.reach(formData_ValidationSchema, name).validate(newValue);
setErrors((prev) => ({ ...prev, [name]: "" }));
} catch (err) {
setErrors((prev) => ({ ...prev, [name]: err.message }));
}
}
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitted(true);
try {
await formData_ValidationSchema.validate(formData, { abortEarly: false });
setErrors({});
setFormSubmitted(true);
} catch (error) {
const errMessage = {};
error.inner.forEach((err) => {
errMessage[err.path] = err.message;
});
setErrors(errMessage);
}
};
return (
<>
<div className="main-wrapper flex justify-center items-center py-4 lg:py-0 w-full min-h-screen">
{/* Form */}
{formSubmitted ? (
<FormSuccess
onBack={() => {
setFormData(initialFormData);
setErrors({});
setFormSubmitted(false);
}}
/>
) : (
<div className="form-wrapper rounded-2xl w-[90%] md:w-[70%] lg:w-[60%] xl:w-[40%]">
{/* Form Heading */}
<div className="form-head">
<h1 className="text-base md:text-lg lg:text-xl xl:text-2xl py-3">
Contact Us
</h1>
</div>
{/* Form inputs container */}
<form className="form-inputs-container" onSubmit={handleSubmit}>
{/* Name Input */}
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-3">
<div className="form-group">
<label htmlFor="name" className="form-label">
Name
</label>
<input
type="text"
id="name"
name="personName"
placeholder="Enter your name"
value={formData.personName}
onChange={handleChange}
/>
{errors.personName && (
<p style={{ color: "red" }}>{errors.personName}</p>
)}
</div>
{/* Email Input */}
<div className="form-group">
<label htmlFor="emails" className="form-label">
Email
</label>
<input
type="email"
id="email"
name="email"
placeholder="Enter your email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && (
<p style={{ color: "red" }}>{errors.email}</p>
)}
</div>
</div>
{/* Phone Input */}
<div className="grid grid-cols-1 md:grid-cols-2 md:gap-3">
<div className="form-group phone-input">
<label htmlFor="phones" className="form-label">
Phone
</label>
<PhoneInput
country={"in"}
value={formData.phoneNumber}
onChange={async (phone) => {
setFormData({ ...formData, phoneNumber: phone });
if (isSubmitted) {
try {
await Yup.reach(
formData_ValidationSchema,
"phoneNumber",
).validate(phone);
setErrors((prev) => ({ ...prev, phoneNumber: "" }));
} catch (err) {
setErrors((prev) => ({
...prev,
phoneNumber: err.message,
}));
}
}
}}
name="phoneNumber"
/>
{errors.phoneNumber && (
<p style={{ color: "red" }}>{errors.phoneNumber}</p>
)}
</div>
{/* Dropdown Input */}
<div className="form-group">
<label htmlFor="services-1.1" className="form-label">
Services
</label>
<select
id="services"
name="serviceSelected"
value={formData.serviceSelected}
onChange={handleChange}
>
<option value="">Select a service</option>
<option value="android_development">
Android Development
</option>
<option value="web_development">Web Development</option>
<option value="digital_marketing">Digital Marketing</option>
<option value="seo_optimization">SEO Optimization</option>
<option value="data_analysis">Data Analysis</option>
</select>
{errors.serviceSelected && (
<p style={{ color: "red" }}>{errors.serviceSelected}</p>
)}
</div>
</div>
{/* Textarea */}
<div className="grid grid-cols-1">
<div className="form-group">
<label htmlFor="messages" className="form-label">
Message
</label>
<textarea
id="message"
name="message"
placeholder="Write your message"
value={formData.message}
onChange={handleChange}
></textarea>
{errors.message && (
<p style={{ color: "red" }}>{errors.message}</p>
)}
</div>
</div>
{/* Checkbox Input */}
<div className="grid grid-cols-1">
<div className="form-group">
<input
type="checkbox"
id="agreement-line"
name="agreement"
checked={formData.agreement}
onChange={handleChange}
/>
<label htmlFor="agreement-line" className="form-label">
I have read and agree to the Terms & Conditions and Privacy
Policy
</label>
{errors.agreement && (
<p style={{ color: "red" }}>{errors.agreement}</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="grid grid-cols-1">
<div className="form-group items-center mt-3 md:mt-0">
<input type="submit" value="Submit" />
</div>
</div>
</form>
</div>
)}
</div>
</>
);
}
export default Form;Explanation:
These are the core functionality and flow parts of the Contact Form:
1. State Management
In this code, we start by importing the useState hook from the React package using import useState from React. The state in a functional component is created and managed by using the useState hook.
A state is a form of data storage that may change over time, such as the contents of a form a user fills out. Using the state, we can get two things: the state's current value (formData) and a method to update it (setFormData).
P.S - Here, the initialFormData object is set as the initial state for the form using useState.
Here, we have four state variables:
- formData – stores all the values entered by the user in the form fields.
- errors – stores error messages for each field if the validation fails.
- formSubmitted – tracks whether the form has been successfully submitted.
- isSubmitted – tracks whether the user has attempted to submit the form, used to validate fields in real time while typing.
This arrangement enables the form to be dynamically updated, errors to be displayed immediately, and the success message to be shown upon submission.
2. Validation Schema with formData_ValidationSchema (Yup)
Why do we use Yup for form validation?
Yup is our preferred form validation tool as it is quick and simple to use. Yup allows developers to specify all the rules in one place rather than writing long, repetitive checks for each field, which saves time and ensures the code is clean and free of errors.
Here, formData_ValidationSchema is a Yup object that defines rules for each form field:
- personName must be at least 3 characters and is required.
- email must match a proper email format and is required.
- phoneNumber must follow a valid international phone format and is required.
- serviceSelected must have a value (user must select a service).
- message must be at least 10 characters long and is required.
- agreement is a checkbox and must be true (user must accept Terms & Conditions).
The form is then used to automatically verify the user's input against this schema. When a field does not conform to the rules, Yup will provide an error message that we can present to the user. In this manner, validation is uniform in all areas, and the form will only be submitted when all the requirements are met.
3. handleChange Function
- The handleChange method is called whenever a user enters a form field or a checkbox. It obtains the input that triggered the event as the name, value, type, and checked properties. It also takes the value of the checked checkbox or the value entered in other fields.
- It then updates the formData state by calling the setFormData method, and only the modified field is updated, leaving the rest of the data unchanged.
- In real time, the function also runs Yup validation on the field if the form is already filled in (isSubmitted is true). If the input passes, it will clear any previously displayed error message, and if it fails, it will set the correct error message in the errors state.
- In this manner, users can view validation messages as they type, and the form remains updated in the state.
4. React Phone Input Handling
- The react-phone-input-2 library provides us with a ready-to-use PhoneInput component (<PhoneInput/>) that we can use. It displays a phone field with country codes with their respective flag icons, making it easy to enter numbers correctly. The current value is retrieved from formdata.phoneNumber, and each time the user types, the onChange event updates the state
- If the form has already been submitted (isSubmitted is true), the number is also checked in real time using Yup. If the input is correct, the error is cleared; otherwise, it displays the relevant error message. In this manner, the phone field is interactive, validated, and user-friendly without additional code.
5. handleSubmit Function
- handleSubmit is a function that runs when the user clicks the Submit button. First, e.preventDefault prevents the page from reloading. Next, setIsSubmitted(true) marks the form as having been submitted at least once, enabling us to display validation errors in real time.
- Then it checks the entire form (formData) using Yup.validate(). This approach accepts the form data as the first parameter and the options object as the second. We have set { abortEarly: false } so that Yup will verify all fields simultaneously and gather all errors, rather than halting at the first one.
- .validate () is an asynchronous process; therefore, the function is declared as an async one, and we wait with the await as follows: The try block is executed when all fields are valid: it removes any prior errors and sets formSubmitted to true, which activates the success component. When the validation process fails, the catch block captures the error. Yup gathers all field errors, and the error state is updated, indicating the error message displayed for each input.
- This will mean the form can be fully validated before submission, users can see all errors simultaneously, and the code remains clean and well-structured.
6. Error Messages
The error messages are text messages indicating what is wrong with each input field, e.g., "Name must be at least 3 characters" or "Enter a valid email".
They are triggered when a failure occurs under the Yup schema. On triggering, they are displayed beneath the respective input to ensure the user is aware of which field needs correction.
7. Conditional Rendering & Passing Data to Child Component
Conditional rendering is used to display the contact form or the success message depending on the formSubmitted state. When it is true, the success component will be displayed, and the form is hidden; when it is false, the form will be displayed to the user.
The FormSuccess child component accepts a callback function (onBack) as a prop from the parent. This enables the child to reset the form and error states in the parent when prompted, demonstrating parent-to-child communication through props.










