State & InteractionIntermediate6 min08 / 12

Forms & Controlled Inputs

Wire form fields to state so React is the single source of truth — the controlled-component pattern.

In plain HTML, an <input> keeps its own value in the DOM. In React we usually make inputs controlled: their value comes from state, and every keystroke updates that state. React becomes the single source of truth for what's in the field.

a controlled input
import { useState } from 'react';

function NameField() {
  const [name, setName] = useState('');
  return (
    <input
      value={name}
      onChange={e => setName(e.target.value)}
      placeholder="Your name"
    />
  );
}

Two bindings make it controlled: value={name} (state → input) and onChange (input → state). Because the value always mirrors state, you can validate, format, or disable the submit button based on it.

Common mistake

value without onChange = read-only

If you set value={name} but forget onChange, React locks the field — typing does nothing, and you'll see a console warning. Always pair value with an onChange (or use defaultValue for an uncontrolled field).

#Handling submit

prevent the default reload
function Form() {
  const [email, setEmail] = useState('');
  function handleSubmit(e) {
    e.preventDefault();      // stop the full-page reload
    console.log('submit', email);
  }
  return (
    <form onSubmit={handleSubmit}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <button>Send</button>
    </form>
  );
}
Quick check

What makes an input a *controlled* component?

Key takeaways

  • Controlled inputs derive their value from state and update it on every change.
  • Bind both `value={state}` and `onChange={e => setState(e.target.value)}`.
  • value without onChange makes the field read-only (and warns).
  • Call `e.preventDefault()` in onSubmit to stop the browser's full-page reload.
Practice challenges
Test yourself · earn XP
0/3
Fix the bug#1

Typing in this input does nothing. Why?

fix-bug
function Field() {
  const [name, setName] = useState('');
  return <input value={name} />;
}
Fix the bug#2

Submitting this form reloads the whole page. What's missing?

fix-bug
function Form() {
  function handleSubmit(e) {
    console.log('sent');
  }
  return <form onSubmit={handleSubmit}><button>Go</button></form>;
}
Fill in the blank#3

Complete the change handler that keeps state in sync with the input.

<input value={q} onChange={e => setQ(e.target.)} />
Your turn
Practice exercise

Build a controlled <textarea> whose current character count is shown below it (e.g. '12 characters'). Store the text in state.

Try it live — edit the code and hit Run to see it rendered:

solution.jsx · editable