2 REACT FRONTEND

Forms & Validation

📝 React Forms & Advanced Validation

Forms are the primary way users pass data to your application. Whether it's a login screen, a complex multi-step checkout, or a massive insurance application, mastering React forms is absolutely critical.

Building a form in raw React using useState is notoriously tedious. In this lesson, we cover both the manual way (to understand the architecture) and the professional way (using libraries).


1️⃣ Controlled Components (The Manual Way)

In standard HTML, an <input> tag manages its own internal state on the screen. React hates this. React demands total, tyrannical control over the UI. A Controlled Component is an input where React completely hijack its value and its typing events.

import { useState } from 'react';

function ControlledLoginForm() {
    // 1. React holds the absolute truth in State
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleSubmit = (e) => {
        // Prevent the browser from wiping the page and doing a hard refresh
        e.preventDefault(); 
        console.log("Sending to API: ", { email, password });
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>Email:</label>
            {/* 2. Hijack the Input */}
            <input 
                type="email" 
                value={email} // The input's text is locked permanently to React's state
                onChange={(e) => setEmail(e.target.value)} // When they type, update React
            />
            
            <label>Password:</label>
            <input 
                type="password" 
                value={password} 
                onChange={(e) => setPassword(e.target.value)} 
            />
            
            <button type="submit">Log In</button>
        </form>
    );
}

The Problem with Controlled Components

If you have a form with 20 fields (First Name, Last Name, Address, City...), writing 20 useState declarations and 20 onChange functions is absurd. The component will also violently re-render the entire massive form every single time the user clicks a single key.


2️⃣ Uncontrolled Components (The Fast Way)

An Uncontrolled Component ignores useState entirely. It lets the HTML input act normally, and uses a useRef to silently grab the value out of the input only at the exact moment the user clicks Submit.

import { useRef } from 'react';

function UncontrolledCheckout() {
    // Creates an empty pointer
    const nameRef = useRef(null);

    const handleSubmit = (e) => {
        e.preventDefault();
        // Reach directly into the DOM node and snatch the value on submit
        const finalName = nameRef.current.value;
        console.log("Purchased by:", finalName);
    };

    return (
        <form onSubmit={handleSubmit}>
            <label>Full Name:</label>
            {/* The input acts normally. It does NOT trigger re-renders when typing! */}
            <input type="text" ref={nameRef} />
            
            <button type="submit">Complete Order</button>
        </form>
    );
}

3️⃣ Advanced Forms: React Hook Form (RHF)

Professional engineers rarely build forms manually anymore. They use React Hook Form (RHF). RHF is an industry-standard library that uses Uncontrolled Components under the hood to completely eliminate re-renders, while providing an incredibly elegant API for validation.

npm install react-hook-form

The RHF Architecture

Instead of tracking state manually, you register your inputs with the RHF engine.

import { useForm } from 'react-hook-form';

function RegistrationForm() {
    // Destructure exactly the tools we need from the Hook
    const { register, handleSubmit, formState: { errors } } = useForm();

    // RHF passes the validated data object directly into our submit function!
    const onSubmitCallback = (data) => {
        console.log("Valid data ready for the Database:", data);
    };

    return (
        // Hooking RHF's handleSubmit AROUND our custom function
        <form onSubmit={handleSubmit(onSubmitCallback)}>
            
            <label>Username (Required)</label>
            {/* We 'register' the input, telling RHF to track it. We also pass validation rules! */}
            <input 
                type="text" 
                {...register("username", { required: "Username is mandatory", minLength: 3 })} 
            />
            {/* If the user violates the rule above, RHF populates the 'errors' object */}
            {errors.username && <span className="text-red-500">{errors.username.message}</span>}

            <label>Age (Must be 18+)</label>
            <input 
                type="number" 
                {...register("age", { required: true, min: 18 })} 
            />
            {errors.age && <span className="text-red-500">You must be an adult.</span>}

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

If you type in this form, nothing re-renders. It is blazing fast, perfectly validated, and scales easily to 100 fields.


4️⃣ Schema Validation: Enter Zod

Writing { required: true, minLength: 8 } works for simple forms. But what if you need complex logic? "Password must contain an uppercase letter, a number, and if the user checked the 'Is Company' box, the Tax ID field becomes mandatory."

We solve this using Zod. Zod is a schema declaration library. You build a mathematical blueprint of what the data must look like, and force RHF to check the data against that blueprint.

npm install zod @hookform/resolvers
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

// 1. Define the mathematical blueprint for the form
const loginSchema = z.object({
    email: z.string().email("Please enter a valid email address"),
    password: z.string().min(8, "Password must be at least 8 characters"),
});

function ZodForm() {
    // 2. Lock RHF to the Zod Schema
    const { register, handleSubmit, formState: { errors } } = useForm({
        resolver: zodResolver(loginSchema)
    });

    const onSubmit = (data) => console.log(data);

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input type="email" {...register("email")} />
            <p>{errors.email?.message}</p>

            <input type="password" {...register("password")} />
            <p>{errors.password?.message}</p>
            
            <button type="submit">Login</button>
        </form>
    );
}

💡 Summary Comparison

ApproachArchitectureRe-rendersUse Case
Controlled Forms (useState)State maps exactly to input values.Catastrophic. Triggers on every keystroke.Tiny 1-2 input forms (like a Search box).
Uncontrolled (useRef)Bypasses React entirely to snatch values from DOM.None.Simple legacy forms.
react-hook-formUses hidden refs under the hood.None.Any professional application form.
RHF + ZodExternal math engine strictly validating RHF outputs.None.Complex Enterprise forms (Sign Up, Checkout, Taxes).

Knowledge Check

Complete this quick quiz to verify your understanding and unlock the next module.