Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
16 minLesson 19 of 40
React Fundamentals

Forms in React

Forms in React

React forms use controlled components — React state is the single source of truth for every input value. This makes validation, conditional logic, and form submission clean and predictable.

Controlled Inputs

function EmailInput() {
    const [email, setEmail] = useState("");
    
    return (
        <input
            type="email"
            value={email}           // controlled by state
            onChange={e => setEmail(e.target.value)}  // update state on change
            placeholder="Enter email"
        />
    );
}

Multi-Field Form

function SignupForm() {
    const [form, setForm] = useState({
        name: "",
        email: "",
        password: "",
        role: "user",
        terms: false
    });
    
    // Generic handler for all fields
    function handleChange(e) {
        const { name, value, type, checked } = e.target;
        setForm(prev => ({
            ...prev,
            [name]: type === "checkbox" ? checked : value
        }));
    }
    
    return (
        <form>
            <input name="name" value={form.name} onChange={handleChange} />
            <input name="email" type="email" value={form.email} onChange={handleChange} />
            <input name="password" type="password" value={form.password} onChange={handleChange} />
            <select name="role" value={form.role} onChange={handleChange}>
                <option value="user">User</option>
                <option value="admin">Admin</option>
            </select>
            <input name="terms" type="checkbox" checked={form.terms} onChange={handleChange} />
        </form>
    );
}

Validation

function RegisterForm() {
    const [form, setForm] = useState({ username: "", email: "", password: "" });
    const [errors, setErrors] = useState({});
    const [submitted, setSubmitted] = useState(false);
    
    function validate(data) {
        const newErrors = {};
        if (!data.username.trim()) newErrors.username = "Username is required";
        else if (data.username.length < 3) newErrors.username = "Must be 3+ characters";
        if (!data.email.includes("@")) newErrors.email = "Valid email required";
        if (data.password.length < 8) newErrors.password = "Must be 8+ characters";
        return newErrors;
    }
    
    function handleChange(e) {
        const { name, value } = e.target;
        setForm(prev => ({ ...prev, [name]: value }));
        // Clear error on change
        if (errors[name]) setErrors(prev => ({ ...prev, [name]: "" }));
    }
    
    async function handleSubmit(e) {
        e.preventDefault();
        const newErrors = validate(form);
        if (Object.keys(newErrors).length > 0) {
            setErrors(newErrors);
            return;
        }
        
        try {
            await registerUser(form);
            setSubmitted(true);
        } catch (err) {
            setErrors({ submit: err.message });
        }
    }
    
    if (submitted) return <p>Registration successful!</p>;
    
    return (
        <form onSubmit={handleSubmit} noValidate>
            <div className="field">
                <label>Username</label>
                <input name="username" value={form.username} onChange={handleChange}
                       className={errors.username ? "error" : ""} />
                {errors.username && <span className="error-msg">{errors.username}</span>}
            </div>
            
            <div className="field">
                <label>Email</label>
                <input name="email" type="email" value={form.email} onChange={handleChange}
                       className={errors.email ? "error" : ""} />
                {errors.email && <span className="error-msg">{errors.email}</span>}
            </div>
            
            <div className="field">
                <label>Password</label>
                <input name="password" type="password" value={form.password} onChange={handleChange}
                       className={errors.password ? "error" : ""} />
                {errors.password && <span className="error-msg">{errors.password}</span>}
            </div>
            
            {errors.submit && <div className="error-banner">{errors.submit}</div>}
            
            <button type="submit">Create Account</button>
        </form>
    );
}

Uncontrolled Inputs with useRef

Sometimes you don't need React to track every keystroke — use useRef for simpler cases:

import { useRef } from "react";

function SearchBox({ onSearch }) {
    const inputRef = useRef(null);
    
    function handleSubmit(e) {
        e.preventDefault();
        onSearch(inputRef.current.value);
    }
    
    return (
        <form onSubmit={handleSubmit}>
            <input ref={inputRef} type="search" placeholder="Search..." />
            <button>Search</button>
        </form>
    );
}

Use react-hook-form or formik for complex production forms — they handle validation, touched state, and submission with less boilerplate.

Next lesson: React Router v6 — client-side navigation in single-page apps.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!