DevAcademy
LearnReactForms & Controlled Inputs
BeginnerReact

Forms & Controlled Inputs

Learn how to build forms in React using controlled input elements tied to state.

Reading Time

16 min

Lesson

Lesson 9 of 42

What is a Controlled Input?

A controlled input’s value is driven entirely by React state, rather than the DOM’s own internal state. The input’s value comes from a state variable, and every keystroke updates that state via onChange.

A Controlled Text Input

import { useState } from "react";

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

  return (
    <input
      value={name}
      onChange={(e) => setName(e.target.value)}
      placeholder="Enter your name"
    />
  );
}

Why Use Controlled Inputs?

Since the input’s value always lives in state, it’s trivial to validate as the user types, format the value, disable submission until valid, or reset the form — all through normal state updates, with a single source of truth.

A Complete Controlled Form

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={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Log In</button>
    </form>
  );
}

Checkboxes and Select Elements

Checkboxes use the checked attribute instead of value, and controlled selects work the same way as text inputs — their value is driven by state.

Controlled Checkbox and Select

function Preferences() {
  const [subscribed, setSubscribed] = useState(false);
  const [plan, setPlan] = useState("free");

  return (
    <>
      <label>
        <input
          type="checkbox"
          checked={subscribed}
          onChange={(e) => setSubscribed(e.target.checked)}
        />
        Subscribe to newsletter
      </label>

      <select value={plan} onChange={(e) => setPlan(e.target.value)}>
        <option value="free">Free</option>
        <option value="pro">Pro</option>
      </select>
    </>
  );
}

Managing Multiple Fields with One State Object

For forms with several fields, a single state object (instead of one useState call per field) can reduce boilerplate, updating just the changed key on each keystroke.

One State Object for the Whole Form

function SignupForm() {
  const [form, setForm] = useState({ name: "", email: "" });

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

  return (
    <form>
      <input name="name" value={form.name} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
    </form>
  );
}

Uncontrolled Inputs Exist Too

An "uncontrolled" input lets the DOM manage its own value, read only when needed (usually via a ref) — simpler for very basic cases, but less flexible than the controlled approach for validation and dynamic UI.

Best Practice

Default to controlled inputs for anything beyond the simplest form — the ability to validate, format, and react to every keystroke is almost always worth the small amount of extra boilerplate.

Interview Questions

Quick Quiz

1. What makes an input "controlled" in React?

2. Which attribute does a controlled checkbox use instead of value?

3. Why might you use one state object instead of separate useState calls for each form field?