React forms use controlled components — form state is managed by React state.

Controlled Input

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

    return (
        <div>
            <input
                value={name}
                onChange={e => setName(e.target.value)}
                placeholder="Enter name"
            />
            <p>Hello, {name || 'stranger'}!</p>
        </div>
    );
}
  

Multiple Inputs

  function ContactForm() {
    const [form, setForm] = useState({
        name: '',
        email: '',
        message: ''
    });

    const handleChange = (e) => {
        const { name, value } = e.target;
        setForm(prev => ({ ...prev, [name]: value }));
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log(form);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value={form.name} onChange={handleChange} />
            <input name="email" type="email" value={form.email} onChange={handleChange} />
            <textarea name="message" value={form.message} onChange={handleChange} />
            <button type="submit">Send</button>
        </form>
    );
}
  

Select and Checkbox

  function Preferences() {
    const [country, setCountry] = useState('us');
    const [newsletter, setNewsletter] = useState(false);

    return (
        <div>
            <select value={country} onChange={e => setCountry(e.target.value)}>
                <option value="us">United States</option>
                <option value="uk">United Kingdom</option>
                <option value="ca">Canada</option>
            </select>

            <label>
                <input
                    type="checkbox"
                    checked={newsletter}
                    onChange={e => setNewsletter(e.target.checked)}
                />
                Subscribe to newsletter
            </label>
        </div>
    );
}
  

Form Validation

  function SignupForm() {
    const [email, setEmail] = useState('');
    const [errors, setErrors] = useState({});

    const validate = () => {
        const newErrors = {};
        if (!email) newErrors.email = 'Email is required';
        else if (!/\S+@\S+\.\S+/.test(email)) newErrors.email = 'Invalid email';
        return newErrors;
    };

    const handleSubmit = (e) => {
        e.preventDefault();
        const newErrors = validate();
        if (Object.keys(newErrors).length > 0) {
            setErrors(newErrors);
            return;
        }
        setErrors({});
        // Submit form...
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                value={email}
                onChange={e => setEmail(e.target.value)}
                className={errors.email ? 'error' : ''}
            />
            {errors.email && <span className="error-msg">{errors.email}</span>}
            <button type="submit">Sign Up</button>
        </form>
    );
}
  

Uncontrolled Components

Use useRef when you don’t need live state updates:

  import { useRef } from 'react';

function QuickForm() {
    const inputRef = useRef(null);

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log(inputRef.current.value);
    };

    return (
        <form onSubmit={handleSubmit}>
            <input ref={inputRef} defaultValue="" />
            <button type="submit">Submit</button>
        </form>
    );
}
  

Prefer controlled components for most forms — they give you full control over validation and UI feedback.