The Codex / Technologies / React / Forms in React

Forms in React

Controlled inputs, form validation, and handling submission - the patterns for building forms that feel native and stay in sync with state.

What it is, in plain English: In React, form inputs are usually 'controlled' -- their value comes from state, and every keystroke updates that state via onChange. This keeps the displayed value and the underlying data always in sync, and makes validation, formatting, and conditional logic straightforward. The alternative ('uncontrolled' inputs using refs) exists for specific cases but controlled inputs are the default approach.

Keeping form inputs in sync with state

The controlled input pattern

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

  return (
    <input
      value={name}                              {/* the input's value IS the state */}
      onChange={(e) => setName(e.target.value)} {/* every keystroke updates state */}
    />
  );
}

// This is "controlled" because React state controls the input's value.
// At every moment, the displayed text and the state value are identical.

Why controlled inputs?

// Controlled inputs let you:
// 1. Validate as the user types
// 2. Format input (e.g. force uppercase, add commas to numbers)
// 3. Conditionally enable/disable the submit button
// 4. Clear the form programmatically

function PhoneInput() {
  const [phone, setPhone] = React.useState("");

  function handleChange(e) {
    const digitsOnly = e.target.value.replace(/\D/g, "");  // strip non-digits
    setPhone(digitsOnly);
  }

  return <input value={phone} onChange={handleChange} placeholder="Numbers only" />;
}

Different input types

function PreferencesForm() {
  const [agree, setAgree] = React.useState(false);
  const [plan, setPlan] = React.useState("free");
  const [bio, setBio] = React.useState("");

  return (
    <form>
      {/* Checkbox uses 'checked', not 'value': */}
      <input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} />

      {/* Select works like text input -- value + onChange: */}
      <select value={plan} onChange={(e) => setPlan(e.target.value)}>
        <option value="free">Free</option>
        <option value="pro">Pro</option>
      </select>

      {/* Textarea also uses value + onChange (not children, unlike HTML): */}
      <textarea value={bio} onChange={(e) => setBio(e.target.value)} />
    </form>
  );
}

Handling submission

function LoginForm() {
  const [email, setEmail] = React.useState("");
  const [password, setPassword] = React.useState("");

  function handleSubmit(e) {
    e.preventDefault();   // stop the page from reloading
    console.log("Logging in with:", email, password);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 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>
  );
}
Try It Yourself (JSX)

Official Sources

The Codex links only to official documentation. No third-party tutorials.