Paramclasses Academy - Blog

Tutorials Details

←
Post 12 of 29
→

How to Handling Forms in React — Controlled Components and Form Validation?

How to Handling Forms in React — Controlled Components and Form Validation?

October 29, 2026

1. Introduction

Forms are an essential part of modern web applications. Users interact with forms to register accounts, log in, submit contact details, search for products, and complete online purchases.

In React, forms require a structured approach to managing input values, handling user interactions, validating data, and processing submissions.

React commonly uses controlled components, where form input values are managed through React state. This allows the application to respond to user input and validate values as they change.

For example, a registration form may need to:

  • Capture the user's name, email, and password.

  • Display validation messages for invalid inputs.

  • Prevent submission when required fields are missing.

  • Show a confirmation after successful validation.

  • Reset the form when needed.

In this tutorial, you will learn how to build React forms from basic input fields to a complete form with state management, validation, and submission handling.

2. Learning Objectives

After completing this tutorial, you will be able to:

  1. Understand how forms work in React.

  2. Create input fields using JSX.

  3. Understand controlled components.

  4. Manage form values using the useState Hook.

  5. Handle onChange, onSubmit, and form events.

  6. Validate user input.

  7. Display conditional validation messages.

  8. Prevent unwanted page reloads during form submission.

  9. Reset forms and display success messages.

  10. Build a complete registration form.

3. Core Concepts and Explanation

3.1 Creating a Basic Form in React

A form in React uses familiar HTML elements, including form, input, label, and button.

function ContactForm() {
  return (
    <form>
      <label htmlFor="name">Name:</label>

      <input
        id="name"
        type="text"
        placeholder="Enter your name"
      />

      <button type="submit">Submit</button>
    </form>
  );
}

export default ContactForm;

Explanation:

  • <form> groups the form controls.

  • <label> describes the input field.

  • htmlFor="name" associates the label with the input whose id is name.

  • placeholder displays a hint inside the input.

  • type="submit" makes the button submit the form.

This creates a basic form, but it does not yet manage the input value through React state or implement custom submission logic.

3.2 What Is a Controlled Component?

A controlled input is an input element whose displayed value is controlled by React state.

For example:

import { useState } from "react";

function NameInput() {
  const [name, setName] = useState("");

  return (
    <div>
      <label htmlFor="name">Your Name:</label>

      <input
        id="name"
        type="text"
        value={name}
        onChange={(event) => setName(event.target.value)}
      />

      <p>Your name is: {name}</p>
    </div>
  );
}

export default NameInput;

How it works:

  1. useState("") initializes the input value as an empty string.

  2. value={name} tells React which value the input should display.

  3. onChange runs when the user changes the input.

  4. event.target.value retrieves the current input value.

  5. setName() updates the state.

  6. React renders the updated value and preview.

The input value and the React state stay synchronized.

3.3 Understanding the onChange Event

The onChange event is commonly used to capture changes in text fields, email fields, and other form controls.

import { useState } from "react";

function EmailInput() {
  const [email, setEmail] = useState("");

  function handleEmailChange(event) {
    setEmail(event.target.value);
  }

  return (
    <div>
      <label htmlFor="email">Email:</label>

      <input
        id="email"
        type="email"
        value={email}
        onChange={handleEmailChange}
      />

      <p>Entered email: {email}</p>
    </div>
  );
}

export default EmailInput;

Here, handleEmailChange receives the event object and extracts the latest input value.

For controlled text inputs, initialize the state with a string, such as "", and keep it a string throughout the component's lifetime.

3.4 Handling Form Submission with onSubmit

When a form is submitted, the browser normally attempts its default submission behavior. In a client-side React form, you will often want to handle the submission in JavaScript instead.

The event.preventDefault() method prevents the browser's default form submission behavior.

import { useState } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");

  function handleSubmit(event) {
    event.preventDefault();

    alert(`Submitted email: ${email}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email:</label>

      <input
        id="email"
        type="email"
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        required
      />

      <button type="submit">Login</button>
    </form>
  );
}

export default LoginForm;

Explanation:

  • onSubmit={handleSubmit} attaches the submission handler to the form.

  • event.preventDefault() prevents the browser's default navigation or submission behavior.

  • The current email value is available through React state.

  • The HTML required attribute prevents submission when the field is empty in a supporting browser.

This example demonstrates form handling only. It does not authenticate the user.

3.5 Managing Multiple Input Fields

A registration form may contain multiple fields. You can store each value in a separate state variable.

import { useState } from "react";

function RegistrationForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  function handleSubmit(event) {
    event.preventDefault();

    console.log({ name, email, password });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Full name"
        value={name}
        onChange={(event) => setName(event.target.value)}
        required
      />

      <input
        type="email"
        placeholder="Email address"
        value={email}
        onChange={(event) => setEmail(event.target.value)}
        required
      />

      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(event) => setPassword(event.target.value)}
        required
      />

      <button type="submit">Register</button>
    </form>
  );
}

export default RegistrationForm;

Each field has its own state and update handler. The submission handler can access all three values.

Security note: Never log real passwords or expose them in client-side logs. This example demonstrates state management; a real registration system must send credentials securely to a trusted backend and handle them appropriately.

3.6 Managing Multiple Fields with One State Object

For forms with several fields, an object can keep related values together.

import { useState } from "react";

function ProfileForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    city: ""
  });

  function handleChange(event) {
    const { name, value } = event.target;

    setFormData((currentData) => ({
      ...currentData,
      [name]: value
    }));
  }

  function handleSubmit(event) {
    event.preventDefault();

    console.log({
      name: formData.name,
      email: formData.email,
      city: formData.city
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Name"
        required
      />

      <input
        name="email"
        type="email"
        value={formData.email}
        onChange={handleChange}
        placeholder="Email"
        required
      />

      <input
        name="city"
        value={formData.city}
        onChange={handleChange}
        placeholder="City"
        required
      />

      <button type="submit">Save Profile</button>
    </form>
  );
}

export default ProfileForm;

Key concepts:

  • Each input has a name matching a property in formData.

  • event.target.name identifies the field that changed.

  • [name]: value updates that property dynamically.

  • The spread operator (...currentData) preserves the other fields.

  • The functional updater works from the latest state.

This approach is convenient for forms with many related inputs.

3.7 Form Validation

Form validation checks whether the entered data meets the application's requirements.

Common examples include:

  • Name must not be empty.

  • Email must have a plausible email format.

  • Password must meet the required length.

  • Password confirmation must match the password.

HTML provides useful built-in validation attributes such as required, minLength, maxLength, and type="email".

You can also implement application-specific validation in JavaScript.

import { useState } from "react";

function EmailValidationForm() {
  const [email, setEmail] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

  function handleSubmit(event) {
    event.preventDefault();
    setSubmitted(true);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="email">Email:</label>

      <input
        id="email"
        type="email"
        value={email}
        onChange={(event) => {
          setEmail(event.target.value);
          setSubmitted(false);
        }}
        required
      />

      <button type="submit">Validate Email</button>

      {submitted && (
        isValidEmail
          ? <p>Email format looks valid.</p>
          : <p>Please enter a valid email address.</p>
      )}
    </form>
  );
}

export default EmailValidationForm;

The regular expression above is a basic demonstration, not a complete email verification system. Real applications should also validate data on the server.

3.8 Displaying Validation Errors

Validation messages can be shown conditionally so users understand how to correct their input.

import { useState } from "react";

function PasswordForm() {
  const [password, setPassword] = useState("");
  const [submitted, setSubmitted] = useState(false);

  const isValidPassword = password.length >= 8;

  function handleSubmit(event) {
    event.preventDefault();
    setSubmitted(true);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="password">Password:</label>

      <input
        id="password"
        type="password"
        value={password}
        onChange={(event) => {
          setPassword(event.target.value);
          setSubmitted(false);
        }}
        required
      />

      {submitted && !isValidPassword && (
        <p>Password must contain at least 8 characters.</p>
      )}

      {submitted && isValidPassword && (
        <p>Password meets the length requirement.</p>
      )}

      <button type="submit">Check Password</button>
    </form>
  );
}

export default PasswordForm;

The form displays a message based on the entered password length. In a real application, the password policy may include additional requirements, and the server must enforce them too.

3.9 Handling Checkboxes and Select Fields

React also supports controlled checkboxes and dropdowns.

Checkbox example:

import { useState } from "react";

function TermsCheckbox() {
  const [accepted, setAccepted] = useState(false);

  return (
    <div>
      <label>
        <input
          type="checkbox"
          checked={accepted}
          onChange={(event) => setAccepted(event.target.checked)}
        />
        I accept the terms and conditions.
      </label>

      <p>{accepted ? "Accepted" : "Not accepted"}</p>
    </div>
  );
}

export default TermsCheckbox;

For checkboxes, use the Boolean checked property and read event.target.checked.

Select example:

import { useState } from "react";

function CourseSelector() {
  const [course, setCourse] = useState("react");

  return (
    <div>
      <label htmlFor="course">Choose a course:</label>

      <select
        id="course"
        value={course}
        onChange={(event) => setCourse(event.target.value)}
      >
        <option value="react">React.js</option>
        <option value="php">PHP</option>
        <option value="javascript">JavaScript</option>
      </select>

      <p>Selected course: {course}</p>
    </div>
  );
}

export default CourseSelector;

The selected option is controlled by the course state variable.

4. Practical Code Examples

Example 1: Live Form Preview

import { useState } from "react";

function LiveProfilePreview() {
  const [name, setName] = useState("");
  const [city, setCity] = useState("");

  return (
    <div>
      <h2>Profile Form</h2>

      <input
        value={name}
        onChange={(event) => setName(event.target.value)}
        placeholder="Enter your name"
      />

      <input
        value={city}
        onChange={(event) => setCity(event.target.value)}
        placeholder="Enter your city"
      />

      <h3>Live Preview</h3>
      <p>Name: {name || "Not provided"}</p>
      <p>City: {city || "Not provided"}</p>
    </div>
  );
}

export default LiveProfilePreview;

What you learn: React state can power an immediate preview of the values entered by the user.

Example 2: Complete Registration Form

import { useState } from "react";

function RegistrationForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
    confirmPassword: ""
  });

  const [errors, setErrors] = useState({});
  const [success, setSuccess] = useState(false);

  function handleChange(event) {
    const { name, value } = event.target;

    setFormData((currentData) => ({
      ...currentData,
      [name]: value
    }));

    setSuccess(false);
  }

  function handleSubmit(event) {
    event.preventDefault();

    const newErrors = {};

    if (!formData.name.trim()) {
      newErrors.name = "Name is required.";
    }

    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
      newErrors.email = "Enter a valid email address.";
    }

    if (formData.password.length < 8) {
      newErrors.password = "Password must be at least 8 characters.";
    }

    if (formData.confirmPassword !== formData.password) {
      newErrors.confirmPassword = "Passwords do not match.";
    }

    setErrors(newErrors);

    if (Object.keys(newErrors).length === 0) {
      setSuccess(true);
    } else {
      setSuccess(false);
    }
  }

  return (
    <div>
      <h2>Create an Account</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">Full Name</label>
          <input
            id="name"
            name="name"
            value={formData.name}
            onChange={handleChange}
            autoComplete="name"
            required
          />
          {errors.name && <p>{errors.name}</p>}
        </div>

        <div>
          <label htmlFor="email">Email</label>
          <input
            id="email"
            name="email"
            type="email"
            value={formData.email}
            onChange={handleChange}
            autoComplete="email"
            required
          />
          {errors.email && <p>{errors.email}</p>}
        </div>

        <div>
          <label htmlFor="password">Password</label>
          <input
            id="password"
            name="password"
            type="password"
            value={formData.password}
            onChange={handleChange}
            autoComplete="new-password"
            required
          />
          {errors.password && <p>{errors.password}</p>}
        </div>

        <div>
          <label htmlFor="confirmPassword">Confirm Password</label>
          <input
            id="confirmPassword"
            name="confirmPassword"
            type="password"
            value={formData.confirmPassword}
            onChange={handleChange}
            autoComplete="new-password"
            required
          />
          {errors.confirmPassword && <p>{errors.confirmPassword}</p>}
        </div>

        <button type="submit">Register</button>
      </form>

      {success && (
        <p>Form validation successful! Your data is ready to be submitted.</p>
      )}
    </div>
  );
}

export default RegistrationForm;

How this example works:

  1. formData stores all input values.

  2. handleChange updates the corresponding field.

  3. handleSubmit prevents the default browser submission.

  4. Validation rules generate an errors object.

  5. Conditional rendering displays the relevant error messages.

  6. The success message appears only when all implemented checks pass.

Important: This example validates data in the browser only. It does not create an account or save user information. Production registration requires server-side validation, secure password handling, and appropriate authentication controls.

Example 3: Resetting a Form

import { useState } from "react";

function FeedbackForm() {
  const initialValues = {
    name: "",
    feedback: ""
  };

  const [formData, setFormData] = useState(initialValues);
  const [submitted, setSubmitted] = useState(false);

  function handleChange(event) {
    const { name, value } = event.target;

    setFormData((currentData) => ({
      ...currentData,
      [name]: value
    }));

    setSubmitted(false);
  }

  function handleSubmit(event) {
    event.preventDefault();

    setSubmitted(true);
    setFormData(initialValues);
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Feedback Form</h2>

      <input
        name="name"
        value={formData.name}
        onChange={handleChange}
        placeholder="Your name"
        required
      />

      <textarea
        name="feedback"
        value={formData.feedback}
        onChange={handleChange}
        placeholder="Your feedback"
        required
      />

      <button type="submit">Submit Feedback</button>

      {submitted && <p>Thank you for your feedback!</p>}
    </form>
  );
}

export default FeedbackForm;

This example clears the form and displays a confirmation. In a production application, submit the feedback to the server and reset the form after the server confirms success rather than before.

5. Real-World Applications

React forms are used in a wide range of applications.

Authentication

  • Login forms.

  • Registration forms.

  • Password reset forms.

  • Multi-factor authentication interfaces.

E-commerce

  • Shipping address forms.

  • Checkout forms.

  • Coupon entry fields.

  • Payment details collected through appropriate secure payment components.

Education platforms

  • Student registration.

  • Course enrollment.

  • Assignment submissions.

  • Feedback forms.

Business applications

  • Customer contact forms.

  • Employee profiles.

  • Search filters.

  • Admin settings.

Important security principle: Client-side validation improves usability, but it cannot be trusted as the only validation layer. Validate all relevant input on the server before saving data or performing sensitive operations.

6. Common Mistakes and Best Practices

Mistake 1: Forgetting event.preventDefault()

Without preventing the default browser behavior when appropriate, a form submission may trigger a page navigation or reload.

Best practice: Handle client-side submission through the form's onSubmit event.

Mistake 2: Forgetting to update state

A controlled input requires an onChange handler that updates its state. Otherwise, the displayed value cannot be edited normally.

Mistake 3: Using value without initializing state correctly

For controlled text inputs, initialize the value with an empty string rather than leaving it undefined and later changing it to a string.

Mistake 4: Using checked incorrectly

Use checked and event.target.checked for controlled checkboxes instead of treating their Boolean state as ordinary text input.

Mistake 5: Mutating form state directly

Avoid assigning directly to a state object's properties. Use the state setter and create a new object.

Mistake 6: Relying only on client-side validation

Browser-side validation can be bypassed. Always validate input on the backend before accepting or processing it.

Mistake 7: Displaying errors unclearly

Validation messages should explain what went wrong and how the user can correct it. Associate labels with inputs and ensure error text is accessible.

Best Practices

  • Use controlled components when the application needs to track and respond to input changes.

  • Use meaningful field names and labels.

  • Keep validation logic organized and easy to test.

  • Use HTML validation attributes where appropriate.

  • Avoid unnecessary state duplication.

  • Show helpful success and error messages.

  • Do not log passwords or sensitive form values.

  • Use server-side validation for production applications.

  • Reset forms at the appropriate point in the submission lifecycle.

7. Practice Exercises

Complete these exercises to reinforce the concepts.

Exercise 1: Name Input

Create a controlled input that displays the entered name immediately below the field.

Exercise 2: Login Form

Create an email and password form. Prevent the default submission and display a confirmation when the basic required-field checks pass.

Exercise 3: Registration Form

Create fields for name, email, password, and confirm password. Display an error when the passwords do not match.

Exercise 4: Checkbox

Create a terms-and-conditions checkbox. Display “Accepted” only when the checkbox is selected.

Exercise 5: Course Selector

Create a dropdown containing React.js, PHP, and JavaScript. Display the selected course dynamically.

Exercise 6: Validation Messages

Create a form that requires a name and validates the email format. Display a separate message for each invalid field.

Exercise 7: Feedback Form

Build a feedback form with name and message fields. Display a confirmation after a successful demonstration submission and reset the input values.

Exercise 8: Multiple Fields with One State Object

Store name, email, and city in one state object. Use a shared change handler to update the correct field.

8. Key Takeaways and Conclusion

In this tutorial, you learned how to create and manage React forms using controlled components, state, and event handlers.

Remember these essential concepts:

  1. Controlled components use React state to manage input values.

  2. onChange captures changes in form controls.

  3. onSubmit handles form submission.

  4. event.preventDefault() prevents the browser's default submission behavior when you are handling the form client-side.

  5. useState can store individual input values or a complete form-data object.

  6. Validation checks whether input satisfies the application's rules.

  7. Conditional rendering can display validation errors and success messages.

  8. Client-side validation must be complemented by server-side validation for production applications.

These concepts form the foundation for building professional registration pages, login interfaces, checkout forms, and data-entry applications in React.

←
Post 12 of 29
→