React Tutorial 0/120 lessons ~6 min read Lesson 26

    Controlled Components

    Controlled components in React: state as source of truth for inputs, checkboxes, selects, multi-field forms, validation, resets, and controlled vs uncontrolled.

    Course progress0%
    Focus
    47 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Controlled Components

    Controlled Components are one of the foundational pieces of modern React engineering.

    Forms are a major part of almost every React application. Users enter names, emails, passwords, search queries, addresses, preferences, and other information. React needs a reliable way to know what the user has entered.

    A controlled component is a form element whose current value is controlled by React state.

    text
    React State
    ↓
    Form Element
    ↑
    onChange
    ↑
    User Input

    React becomes the source of truth for the form value.

    Learning Objectives

    1. What controlled components are and how the state ↔ input cycle works
    2. Controlled vs uncontrolled form elements
    3. Text, textarea, select, checkbox, and radio patterns
    4. Multi-field form objects, spreads, and functional updates
    5. Validation, derived UI, resets, limits, and transforms
    6. Common mistakes, best practices, and interview questions

    1. What Is a Controlled Component?

    A normal input lets the browser manage the value:

    tsx
    <input type="text" />

    In a controlled React input, the value comes from state:

    tsx
    import { useState } from "react";
    function NameForm() {
    const [name, setName] = useState("");
    return (
    <input
    value={name}
    onChange={(event) => {
    setName(event.target.value);
    }}
    />
    );
    }
    text
    User types
    ↓
    onChange
    ↓
    setName()
    ↓
    name state changes
    ↓
    React renders
    ↓
    value={name}
    ↓
    Input displays updated value

    2. React as the Source of Truth

    In a controlled component, React state is the source of truth for the form value.

    text
    React State
    │
    ▼
    value={state}
    │
    ▼
    <input>
    │
    User types
    │
    ▼
    onChange
    │
    ▼
    setState()
    │
    └──────────────► React State

    3. Basic Controlled Input

    tsx
    import { useState } from "react";
    function App() {
    const [name, setName] = useState("");
    return (
    <div>
    <input
    value={name}
    onChange={(event) => {
    setName(event.target.value);
    }}
    />
    <p>Your name: {name}</p>
    </div>
    );
    }

    If the user types Rahul, the UI displays Your name: Rahul. React knows the current value at all times.

    4. Why Controlled Components Are Useful

    They make it easy to:

    • Validate user input
    • Display live values and character counts
    • Disable/enable buttons conditionally
    • Reset forms and transform input
    • Show validation errors and submit data
    • Control dependent fields and synchronize inputs
    tsx
    <p>Characters: {name.length}</p>

    5. Controlled vs Uncontrolled

    Controlled

    tsx
    <input
    value={name}
    onChange={handleChange}
    />

    Uncontrolled

    tsx
    <input ref={inputRef} />
    text
    Controlled: React State → Input ← onChange
    Uncontrolled: DOM → Input value → ref → React reads when needed

    6. Controlled Text Input

    tsx
    function Profile() {
    const [name, setName] = useState("");
    function handleChange(event) {
    setName(event.target.value);
    }
    return (
    <input
    type="text"
    value={name}
    onChange={handleChange}
    />
    );
    }

    Both value and onChange are generally needed for a fully controlled input.

    7. The onChange Handler

    tsx
    function handleChange(event) {
    console.log(event.target.value);
    setName(event.target.value);
    }

    As the user types React, values progress: R → Re → Rea → Reac → React.

    8. Controlled Inputs with Initial Values

    tsx
    const [name, setName] = useState("Rahul");
    <input
    value={name}
    onChange={handleChange}
    />
    text
    Existing profile → Load data → Initialize state → Controlled form → User edits → State updates

    9. Controlled Inputs and Validation

    tsx
    function EmailForm() {
    const [email, setEmail] = useState("");
    const [error, setError] = useState("");
    function handleChange(event) {
    const value = event.target.value;
    setEmail(value);
    if (!value.includes("@")) {
    setError("Enter a valid email");
    } else {
    setError("");
    }
    }
    return (
    <div>
    <input type="email" value={email} onChange={handleChange} />
    {error && <p>{error}</p>}
    </div>
    );
    }

    10. Controlled Components and Derived UI

    tsx
    function SearchForm() {
    const [query, setQuery] = useState("");
    return (
    <div>
    <input
    value={query}
    onChange={(event) => setQuery(event.target.value)}
    />
    <p>Searching for: {query}</p>
    <button disabled={query.trim() === ""}>
    Search
    </button>
    </div>
    );
    }

    11. Controlled Textarea

    tsx
    function MessageForm() {
    const [message, setMessage] = useState("");
    return (
    <>
    <textarea
    value={message}
    onChange={(event) => setMessage(event.target.value)}
    />
    <p>{message.length} characters</p>
    </>
    );
    }

    12. Controlled Select

    tsx
    function CountrySelect() {
    const [country, setCountry] = useState("");
    return (
    <select
    value={country}
    onChange={(event) => setCountry(event.target.value)}
    >
    <option value="">Select a country</option>
    <option value="india">India</option>
    <option value="usa">USA</option>
    <option value="uk">United Kingdom</option>
    </select>
    );
    }

    13. Controlled Checkboxes

    Checkboxes use checked instead of value:

    tsx
    function Notifications() {
    const [enabled, setEnabled] = useState(false);
    return (
    <label>
    <input
    type="checkbox"
    checked={enabled}
    onChange={(event) => setEnabled(event.target.checked)}
    />
    Enable notifications
    </label>
    );
    }
    text
    Text input → value
    Checkbox → checked

    14. Controlled Radio Buttons

    tsx
    function GenderForm() {
    const [gender, setGender] = useState("");
    return (
    <div>
    <label>
    <input
    type="radio"
    name="gender"
    value="male"
    checked={gender === "male"}
    onChange={(event) => setGender(event.target.value)}
    />
    Male
    </label>
    <label>
    <input
    type="radio"
    name="gender"
    value="female"
    checked={gender === "female"}
    onChange={(event) => setGender(event.target.value)}
    />
    Female
    </label>
    </div>
    );
    }

    15. Multiple Controlled Inputs

    tsx
    const [form, setForm] = useState({
    name: "",
    email: "",
    phone: ""
    });
    <input name="name" value={form.name} onChange={handleChange} />
    <input name="email" value={form.email} onChange={handleChange} />
    <input name="phone" value={form.phone} onChange={handleChange} />

    16. One Handler for Multiple Inputs

    tsx
    function handleChange(event) {
    const { name, value } = event.target;
    setForm((currentForm) => ({
    ...currentForm,
    [name]: value
    }));
    }

    The name attribute determines which property should change.

    17. Why the Spread Operator Is Important

    Do not replace the entire object with only the changed field — other properties would be lost. Spread preserves them:

    tsx
    setForm((currentForm) => ({
    ...currentForm,
    email: "rahul@example.com"
    }));
    text
    Existing form (name, email, phone)
    ↓
    ...currentForm + changed field
    ↓
    New form object

    18. Functional State Updates

    tsx
    setForm((currentForm) => ({
    ...currentForm,
    [name]: value
    }));

    Especially useful when multiple state updates may occur close together.

    19. Controlled Components and Form Submission

    tsx
    function LoginForm() {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");
    function handleSubmit(event) {
    event.preventDefault();
    console.log({ email, password });
    }
    return (
    <form onSubmit={handleSubmit}>
    <input
    type="email"
    value={email}
    onChange={(event) => setEmail(event.target.value)}
    />
    <input
    type="password"
    value={password}
    onChange={(event) => setPassword(event.target.value)}
    />
    <button type="submit">Login</button>
    </form>
    );
    }

    20. Controlled Components and Resetting

    tsx
    const initialForm = {
    name: "",
    email: "",
    password: ""
    };
    const [form, setForm] = useState(initialForm);
    function handleReset() {
    setForm(initialForm);
    }
    text
    setForm(initialForm) → State changes → React renders → Input values reset

    21. Controlled Components and Conditional UI

    tsx
    function AccountForm() {
    const [accountType, setAccountType] = useState("");
    return (
    <div>
    <select
    value={accountType}
    onChange={(event) => setAccountType(event.target.value)}
    >
    <option value="">Select account type</option>
    <option value="personal">Personal</option>
    <option value="business">Business</option>
    </select>
    {accountType === "business" && (
    <input placeholder="Company name" />
    )}
    </div>
    );
    }

    22. Controlled Components and Character Limits

    tsx
    function MessageBox() {
    const [message, setMessage] = useState("");
    function handleChange(event) {
    const value = event.target.value;
    if (value.length <= 200) {
    setMessage(value);
    }
    }
    return (
    <div>
    <textarea value={message} onChange={handleChange} />
    <p>{message.length} / 200</p>
    </div>
    );
    }

    23. Transforming Input

    tsx
    function NameInput() {
    const [name, setName] = useState("");
    function handleChange(event) {
    setName(event.target.value.toUpperCase());
    }
    return (
    <input value={name} onChange={handleChange} />
    );
    }

    Typing react becomes REACT. Be thoughtful — aggressive transforms can affect cursor position and UX.

    24. Controlled Number Inputs

    Even with type="number", event.target.value is generally a string. Convert intentionally when needed; keeping string state allows empty values naturally.

    tsx
    const [age, setAge] = useState("");
    const numericAge = Number(age);

    25. Controlled Components and Validation State

    tsx
    const [form, setForm] = useState({
    email: "",
    password: ""
    });
    const [errors, setErrors] = useState({});
    {errors.email && <p>{errors.email}</p>}

    Keep user input separate from validation messages.

    26. Controlled Components and Loading State

    tsx
    const [isSubmitting, setIsSubmitting] = useState(false);
    async function handleSubmit(event) {
    event.preventDefault();
    setIsSubmitting(true);
    try {
    await saveForm(form);
    } finally {
    setIsSubmitting(false);
    }
    }
    <button type="submit" disabled={isSubmitting}>
    {isSubmitting ? "Saving..." : "Save"}
    </button>
    text
    Form data → Validation → Submission state → Result

    27. Controlled vs Uncontrolled: Comparison

    FeatureControlledUncontrolled
    Source of truthReact stateDOM
    Value accessStateRef / DOM
    value / onChangeCommonly usedUsually not / optional
    Live validationEasyLess direct
    Conditional UIEasyLess direct
    ResetUpdate stateDOM / reset methods
    ComplexityGood for complex logicUseful for simpler cases

    Neither is universally better — choose based on what the component needs.

    28. When Controlled Components Are a Good Choice

    1. Live validation
    2. Value affects other UI
    3. Disable controls based on fields
    4. Reset fields via state
    5. Transform / normalize data
    6. Centralized multi-input form state

    29. When an Uncontrolled Component May Be Useful

    • You do not need to react to every keystroke
    • The value is only needed at submission time
    • The form is simple or ref-based access is more convenient
    tsx
    const inputRef = useRef(null);
    function handleSubmit(event) {
    event.preventDefault();
    console.log(inputRef.current.value);
    }

    30. Common Mistake — Controlled Without onChange

    tsx
    // Read-only in practice
    <input value={name} />
    // Editable
    <input
    value={name}
    onChange={(event) => setName(event.target.value)}
    />

    31. Common Mistake — Switching Modes

    Avoid switching between value={undefined} and a string later. Initialize text inputs with "" so they are controlled from the start.

    tsx
    const [name, setName] = useState("");

    32. Common Mistake — Mutating Form State

    tsx
    // Avoid
    form.email = "new@example.com";
    // Instead
    setForm((currentForm) => ({
    ...currentForm,
    email: "new@example.com"
    }));

    33. Common Mistake — Replacing the Entire Object

    tsx
    // Loses other fields
    setForm({ email: "new@example.com" });
    // Preserves other fields
    setForm((currentForm) => ({
    ...currentForm,
    email: "new@example.com"
    }));

    34. Common Mistake — Incorrect Checkbox Logic

    tsx
    // Bad for controlled checkbox
    <input type="checkbox" value={enabled} onChange={handleChange} />
    // Correct
    <input
    type="checkbox"
    checked={enabled}
    onChange={(event) => setEnabled(event.target.checked)}
    />

    35. Common Mistake — Overengineering

    For a small form, separate state variables may be perfectly readable. Introduce abstractions when complexity justifies them.

    tsx
    const [name, setName] = useState("");
    const [email, setEmail] = useState("");

    36. Practical Example — Registration Form

    tsx
    import { useState } from "react";
    function RegistrationForm() {
    const [form, setForm] = useState({
    name: "",
    email: "",
    password: ""
    });
    const [error, setError] = useState("");
    function handleChange(event) {
    const { name, value } = event.target;
    setForm((currentForm) => ({
    ...currentForm,
    [name]: value
    }));
    }
    function handleSubmit(event) {
    event.preventDefault();
    if (!form.name.trim()) {
    setError("Name is required");
    return;
    }
    if (!form.email.trim()) {
    setError("Email is required");
    return;
    }
    if (!form.password) {
    setError("Password is required");
    return;
    }
    setError("");
    console.log("Registration:", form);
    }
    return (
    <form onSubmit={handleSubmit}>
    <input id="name" name="name" value={form.name} onChange={handleChange} />
    <input
    id="email"
    name="email"
    type="email"
    value={form.email}
    onChange={handleChange}
    />
    <input
    id="password"
    name="password"
    type="password"
    value={form.password}
    onChange={handleChange}
    />
    {error && <p>{error}</p>}
    <button type="submit">Register</button>
    </form>
    );
    }

    37. Practical Example — Profile Editor

    tsx
    function ProfileEditor() {
    const [profile, setProfile] = useState({
    name: "Rahul",
    email: "rahul@example.com",
    bio: "Frontend developer"
    });
    function handleChange(event) {
    const { name, value } = event.target;
    setProfile((currentProfile) => ({
    ...currentProfile,
    [name]: value
    }));
    }
    function handleSave(event) {
    event.preventDefault();
    console.log("Updated profile:", profile);
    }
    return (
    <form onSubmit={handleSave}>
    <input name="name" value={profile.name} onChange={handleChange} />
    <input name="email" value={profile.email} onChange={handleChange} />
    <textarea name="bio" value={profile.bio} onChange={handleChange} />
    <button type="submit">Save Profile</button>
    </form>
    );
    }

    38. Practical Example — Dynamic Form

    tsx
    function AccountForm() {
    const [accountType, setAccountType] = useState("");
    const [company, setCompany] = useState("");
    return (
    <form>
    <select
    value={accountType}
    onChange={(event) => setAccountType(event.target.value)}
    >
    <option value="">Select</option>
    <option value="personal">Personal</option>
    <option value="business">Business</option>
    </select>
    {accountType === "business" && (
    <input
    value={company}
    onChange={(event) => setCompany(event.target.value)}
    placeholder="Company name"
    />
    )}
    </form>
    );
    }

    39. Controlled Components Mental Model

    text
    React State
    │
    │ value / checked
    ▼
    Form Element
    │
    │ User interaction
    ▼
    onChange
    │
    ▼
    Event Handler
    │
    ▼
    setState()
    │
    └───────────────┐
    ▼
    React State
    text
    Text: state → value, event → setState
    Checkbox: state → checked, event → setState
    Select: state → value, event → setState

    40. Controlled Components in Real Applications

    • Login — email/password → validation → submit
    • Search — query → controlled input → filter
    • Checkout — address/payment → validate → submit order
    • Profile editing — load data → controlled inputs → save
    • Settings — checkboxes/selects/toggles → preferences

    41. Best Practices

    1. Keep React state as the source of truth (value + onChange)
    2. Initialize text inputs with ""
    3. Use immutable / functional updates
    4. Use checked for checkboxes
    5. Organize form state (separate vars vs object as clarity requires)
    6. Separate validation when it becomes complex
    7. Avoid unnecessary abstraction on simple forms
    8. Choose controlled or uncontrolled intentionally — do not mix accidentally

    42. Interview Questions

    1. What is a controlled component?

    A form element whose value is controlled by React state.

    2. What is the source of truth?

    React state.

    3. How does a controlled input work?

    State provides value; onChange updates state.

    4. value without onChange?

    The input becomes effectively read-only unless other logic changes the value.

    5. Controlled vs uncontrolled?

    Controlled stores value in React state; uncontrolled lets the DOM hold it (often via refs).

    6. Multiple controlled inputs?

    tsx
    function handleChange(event) {
    const { name, value } = event.target;
    setForm((currentForm) => ({
    ...currentForm,
    [name]: value
    }));
    }

    7. How do you control a checkbox?

    checked={value} and event.target.checked.

    8. How do you reset a controlled form?

    tsx
    setForm(initialForm);

    9. Why not mutate form state?

    Update through setters so React can track and render the update.

    10. Must all forms be controlled?

    No — choose based on requirements.

    43. Practice Exercises

    1. Controlled name — display "You entered: …"
    2. Controlled email — error when empty
    3. Character counter — textarea with 200 limit (45 / 200)
    4. Checkbox — disable Submit until terms accepted
    5. Select — country selector; show selection below
    6. Multiple inputs — name, email, phone, city; one state object + one handler
    7. Registration — required fields, email format, password length, matching passwords
    8. Dynamic form — Business shows Company Name
    9. Edit profile — initial data for name/email/bio; edit and save

    44. Quick Cheat Sheet

    ElementControlled PropertyEvent Value
    Text / email / password / number / textarea / selectvalueevent.target.value
    Checkboxcheckedevent.target.checked
    Radiocheckedevent.target.value
    Form—onSubmit
    ResetState updatesetForm(initialForm)

    Key Takeaway

    A controlled component is a form element whose value is managed by React state.

    tsx
    const [value, setValue] = useState("");
    <input
    value={value}
    onChange={(event) => setValue(event.target.value)}
    />
    text
    User Input → onChange → setState() → React State → value / checked → Form Element

    A controlled component is controlled by React state, not by the DOM alone.

    That visibility makes validation, conditional rendering, resetting, submission, and dynamic form behavior much easier. Next: more complex form validation, custom inputs, reusable form components, and advanced form patterns.

    Ready to mark this lesson complete?Track your journey across the entire course.