SyntaxStudy
Sign Up
React Controlled Forms with useState
React Beginner 1 min read

Controlled Forms with useState

A controlled input is one whose value is driven entirely by React state. You set the value prop to a state variable and update state in the onChange handler, making React the single source of truth for the input's content. This gives you immediate access to the current value anywhere in the component, enables real-time validation, and makes it easy to reset the form programmatically. For forms with many fields, storing all values in a single state object reduces the number of state variables. The spread-and-override pattern inside setState — copying the previous state and overriding only the changed key — keeps the update handler concise. Using the input's name attribute as the key allows you to write a single generic handler for all fields. Controlled forms work well with inline validation: derive error messages from the current state during render rather than imperatively setting error text in handlers. This ensures the displayed validation is always consistent with the visible values.
Example
import { useState } from 'react';

function RegistrationForm() {
    const [fields, setFields] = useState({
        username: '',
        email:    '',
        password: '',
    });
    const [submitted, setSubmitted] = useState(false);

    // Single handler for all inputs
    function handleChange(e) {
        const { name, value } = e.target;
        setFields(prev => ({ ...prev, [name]: value }));
    }

    // Derived validation (computed during render)
    const errors = {
        username: fields.username.length < 3 ? 'Min 3 characters' : '',
        email:    !fields.email.includes('@') ? 'Invalid email'   : '',
        password: fields.password.length < 8  ? 'Min 8 characters': '',
    };
    const isValid = Object.values(errors).every(e => e === '');

    function handleSubmit(e) {
        e.preventDefault();
        if (!isValid) return;
        setSubmitted(true);
        console.log('Submitting', fields);
    }

    if (submitted) return <p>Registration successful!</p>;

    return (
        <form onSubmit={handleSubmit}>
            {(['username', 'email', 'password']).map(field => (
                <div key={field}>
                    <input
                        name={field}
                        type={field === 'password' ? 'password' : 'text'}
                        value={fields[field]}
                        onChange={handleChange}
                        placeholder={field}
                    />
                    {errors[field] && <span>{errors[field]}</span>}
                </div>
            ))}
            <button type="submit" disabled={!isValid}>Register</button>
        </form>
    );
}