React Forms: Controlled vs Uncontrolled

When should you control form state? When should you let the DOM handle it? Let's break it down.

Two Approaches

Controlled

React owns the form state:

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

Every keystroke updates React state, which re-renders the input with the new value.

Uncontrolled

The DOM owns the state:

function UncontrolledForm() {
  const inputRef = useRef();
  
  const handleSubmit = () => {
    const email = inputRef.current.value;
    // do something with email
  };
  
  return <input ref={inputRef} defaultValue="" />;
}

You read the value when you need it, not on every change.

When to Use Controlled

  • **Instant validation** - Validate as user types
  • **Conditional disabling** - Enable submit only when valid
  • **Format as you type** - Phone numbers, credit cards
  • **Multiple fields that depend on each other**
function PaymentForm() {
  const [amount, setAmount] = useState('');
  const [tip, setTip] = useState(0);
  
  const total = parseFloat(amount || 0) + tip;
  
  return (
    <>
      <input 
        value={amount} 
        onChange={e => setAmount(e.target.value)}
      />
      <select value={tip} onChange={e => setTip(Number(e.target.value))}>
        <option value={0}>No tip</option>
        <option value={5}>$5</option>
      </select>
      <p>Total: ${total}</p>
    </>
  );
}

When to Use Uncontrolled

  • **Simple forms** - Just submit and get values
  • **File inputs** - These are always uncontrolled
  • **Performance** - Less re-rendering
function SimpleForm() {
  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData);
    console.log(data); // { email: '...', name: '...' }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" />
      <input name="name" type="text" />
      <button type="submit">Submit</button>
    </form>
  );
}

FormData is underrated!

My Default

Start uncontrolled. Add control when you need the features.

For complex forms, use React Hook Form - it gives you the best of both worlds.

Back to Journal