Forms and Controlled Components

Introduction

In React, most form inputs are controlled: their value comes from state and onChange updates that state. This chapter covers text fields, checkboxes, radio groups, selects, submit handling, and simple validation—without a form library.

Prerequisites

Controlled Text Inputs

Code explanation:

  • value + onChange keep React state as the single source of truth
  • Use htmlFor / id to link labels—Vue uses for in templates; JSX uses htmlFor
  • Trim on submit: setEmail(e.target.value.trim()) or validate before POST

Compare Vue v-model in Forms and v-model.

Submit Handler

Code explanation:

  • e.preventDefault() stops full page reload
  • Show field-level or form-level errors with conditional JSX

Checkbox and Radio

Single checkbox:

tsx
const [agreed, setAgreed] = useState(false);
 
<label>
  <input
    type="checkbox"
    checked={agreed}
    onChange={(e) => setAgreed(e.target.checked)}
  />
  I agree to the terms
</label>

Multiple checkboxes (array):

Radio group:

Select

tsx
const [country, setCountry] = useState("us");
 
<select
  value={country}
  onChange={(e) => setCountry(e.target.value)}
>
  <option value="us">United States</option>
  <option value="ca">Canada</option>
  <option value="uk">United Kingdom</option>
</select>

Uncontrolled Inputs (When Needed)

Use useRef for file inputs or integrating non-React widgets:

tsx
import { useRef } from "react";
 
const fileRef = useRef<HTMLInputElement>(null);
 
<input ref={fileRef} type="file" accept="image/*" />;
 
// Read on submit: fileRef.current?.files?.[0]

Most text fields should stay controlled for predictable validation UI.

One Object for Form State

tsx
type FormState = { email: string; password: string };
 
const [form, setForm] = useState<FormState>({ email: "", password: "" });
 
function updateField<K extends keyof FormState>(key: K, value: FormState[K]) {
  setForm((f) => ({ ...f, [key]: value }));
}
 
<input
  value={form.email}
  onChange={(e) => updateField("email", e.target.value)}
/>

Validation (Without a Library)

ApproachUse
HTML5 required, minLength, patternQuick client hints
State errors objectCustom messages per field
Validate on submitSimple login/signup forms

Tip

Libraries Later

React Hook Form and Formik help large forms—learn controlled patterns first.

FAQ

v-model equivalent?

value + onChange (or checked + onChange for checkboxes).

Input lags behind typing?

Do not reset value from props without syncing—avoid fighting controlled state.

Number input?

tsx
onChange={(e) => setAge(Number(e.target.value) || 0)}

Or keep as string until submit.

Autofocus?

tsx
<input autoFocus />  // camelCase in JSX

Next chapter?

Composition, Props, and Lifting State.