React Controlled Components: Inputs, Checkboxes, and Forms

Build React controlled inputs with value, checked, and onChange. Includes a complete form, reset behavior, number fields, and fixes for uncontrolled-input warnings.

Last reviewed: November 2026

·React 18React 19
✅
Quick Understanding: A controlled component is a form input whose value is controlled by React state. The state is the single source of truth. Every keystroke calls onChange, which updates state, which updates the input. This gives you complete control over the form data at all times.

🔍 What Is It?

A controlled component keeps its value in React state:

const [email, setEmail] = useState('');
<input value={email} onChange={e => setEmail(e.target.value)} />

Contrast with an uncontrolled component where the DOM owns the value:

const inputRef = useRef<HTMLInputElement>(null);
<input ref={inputRef} />
// read when mounted: inputRef.current?.value

Both approaches are supported. Choose controlled state when rendering or validation depends on the current value; choose an uncontrolled input with defaultValue, a ref, or FormData when you only need its value at submission.

Choose the right controlled prop

ControlState propChange handler reads
Text input or textareavalue as a stringevent.target.value
Checkboxchecked as a booleanevent.target.checked
Selectvalue matching an optionevent.target.value
Number inputvalue as a string while editingevent.target.value; parse when needed

Initialize a controlled text value to an empty string, not undefined or null. Keep its ownership stable for that input's lifetime. defaultValue and defaultChecked set initial uncontrolled values; they do not keep an input synchronized with later state changes.

🎯 Why Do We Need It?

Controlled components give you:

  • →Real-time validation — validate as the user types
  • →Conditional rendering — show/hide fields based on other field values
  • →Pre-filling — populate forms from API data
  • →Programmatic reset — clear the form after submission
  • →Derived state — compute dependent fields automatically
  • →Testability — test form behaviour by simulating state changes

🏗️ What Happens in Real Projects?

In a claims submission form:

  • →Multi-step form where step 2 data depends on step 1 selections
  • →Real-time field validation (email format, phone number, date ranges)
  • →Auto-populate fields from a policy lookup
  • →Conditional fields (show "secondary claimant" section only if "joint claim" is checked)
  • →Calculate premium estimate as the user fills in coverage details

⚙️ How It Works

The controlled input cycle:

  1. State holds the current value: const [value, setValue] = useState('')
  2. Input renders with the state value: <input value={value} />
  3. User types a character
  4. onChange fires: onChange={e => setValue(e.target.value)}
  5. State updates
  6. React re-renders the input with the new value

Update the state backing value or checked synchronously in onChange. Debounce an expensive search request or derived calculation separately rather than delaying the input's own state update. For an intentionally read-only input, provide readOnly explicitly.

Number fields and resetting

Keep a number input's editable state as text so an empty field remains representable. Convert and validate at the boundary where a number is required; Number('') is 0, so test emptiness first. A native form reset does not replace React state: reset the controlled state as part of your reset handler.

💻 Example

Controlled Forms in Production — A complete native-input example followed by an enterprise form excerpt using project UI components.

tsx
import { useState, type FormEvent } from 'react';

const initialProfile = { name: '', role: 'reader', bio: '', subscribed: false };

export default function ControlledProfileForm() {
  const [profile, setProfile] = useState(initialProfile);
  const [submitted, setSubmitted] = useState('');

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setSubmitted(JSON.stringify(profile));
  }

  return (
    <form onSubmit={handleSubmit} onReset={() => {
      setProfile({ ...initialProfile });
      setSubmitted('');
    }}>
      <label>Name
        <input name="name" required value={profile.name}
          onChange={event => setProfile(previous => ({ ...previous, name: event.target.value }))} />
      </label>
      <label>Role
        <select name="role" value={profile.role}
          onChange={event => setProfile(previous => ({ ...previous, role: event.target.value }))}>
          <option value="reader">Reader</option>
          <option value="editor">Editor</option>
        </select>
      </label>
      <label>Bio
        <textarea name="bio" value={profile.bio}
          onChange={event => setProfile(previous => ({ ...previous, bio: event.target.value }))} />
      </label>
      <label>
        <input name="subscribed" type="checkbox" checked={profile.subscribed}
          onChange={event => setProfile(previous => ({ ...previous, subscribed: event.target.checked }))} />
        Subscribe
      </label>
      <button type="submit">Save</button>
      <button type="reset">Reset</button>
      <output aria-live="polite">{submitted}</output>
    </form>
  );
}

🤔 Common Confusions

❓ What is the difference between controlled and uncontrolled components?

A controlled input receives value or checked from React state and updates that state in onChange. An uncontrolled input keeps its current value in the DOM and can be read through a ref or FormData. Both are valid; choose based on what the UI needs to observe and update.

❓ Why does my input become read-only?

If you pass a value prop without an onChange handler, React makes the input read-only (it controls the value but nothing updates it). Fix: always pair value with onChange. If you want an initial value that the user can then freely edit, use defaultValue instead of value.

🚨 Common Production Issues

**Typing lag** — A state update can re-render an expensive surrounding tree. Keep input state near the form, profile slow work, and defer expensive dependent UI when appropriate. A form library can help, but changing libraries is not the first diagnostic step.

**Bug: Forgetting onChange** — A value prop without onChange makes the input read-only. React will warn in development but it is easy to miss.

**Mutation or stale state** — Do not assign form.field directly. When the new object depends on the previous value, use a functional update such as setForm(previous => ({ ...previous, field: value })).

**Uncontrolled-to-controlled warning** — An initial undefined value later becomes a string, or checked changes from undefined to a boolean. Initialize deliberately or normalize missing API values at the boundary.

✅ Best Practices

Use a single state object for related form fields

Clear field errors on change for better UX

Validate on submit, not on every change (unless real-time feedback is needed)

Retain browser validation where it meets the form requirements; use noValidate only when deliberately providing equivalent custom feedback.

Use checked for checkboxes and value for text; keep the state type stable.

When API data arrives, normalize missing values and avoid overwriting edits on every background refetch.

🏛️ Architect's Note

Senior Architect's Perspective

Choose form ownership based on behavior, not a fixed field-count cutoff. Controlled state is useful when other UI depends on current edits. Uncontrolled inputs can simplify submit-only collection. Libraries add field registration, validation, and subscription models, but still need deliberate defaults, reset behavior, accessibility, and a policy for incoming server data.

💬 Frequently Asked Questions

What makes a React input controlled?

Passing value for a text input or checked for a checkbox makes React supply the current value. Pair it with an onChange handler that synchronously updates the backing state, or mark it intentionally readOnly.

Why does React warn about changing an uncontrolled input to controlled?

The input starts without a defined controlled value and later receives one. Initialize text state to an empty string and checkbox state to a boolean, and normalize missing API values before passing them to the input.

How do I control a checkbox?

Pass a boolean checked prop and update it from event.target.checked. The value prop describes a checkbox form value; it does not control whether the checkbox is selected.

Should I store a number input as a number or a string?

Keeping the editable value as a string allows an empty field while typing. Parse when the application needs a number, check emptiness explicitly, and reject non-finite or out-of-range values. Converting every change directly with Number can turn an empty string into zero.

How do I reset a controlled form?

Reset the React state that supplies the field values. A reset button can call an onReset handler that restores initial state and clears submission or error state. Resetting only DOM fields does not replace the React source of truth.

How do I pre-fill a form from API data?

Normalize fields such as name to data.name ?? an empty string. Decide when the form should adopt new data: initialization, an explicit reload, or a record change. Avoid resetting edits on every background refetch; use a dirty-state guard or remount a form keyed by record identity when that is the intended behavior.

How do I handle file inputs?

File inputs cannot be controlled (you cannot set their value). Use an uncontrolled approach with a ref or React Hook Form's register(). Read the file from the input: e.target.files[0]. Store the File object in state for upload.

🎯 Key Takeaways

1Controlled inputs have value + onChange — React state is the source of truth
2Always pair value with onChange — value alone makes the input read-only
3Use a single state object for form fields, spread for updates
4Both controlled and uncontrolled forms are valid; select based on behavior and measured performance
5Validate on submit; clear errors on change for good UX

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide React learning path

Browse the full React topic collection and learning order on SplashRide.

React Interview Prep

Practice React interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Python Developer Career Path

A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.

React Hook Form

Master React Hook Form — the industry-standard library for forms in React. Learn registration, validation with Zod, error handling, and the patterns used in enterprise applications.

State

Understand React state — the internal memory of a component that drives UI updates. Learn when to use local state, how to structure it, and the patterns senior engineers use to avoid the most common state bugs.