React · State & Events

Forms

Forms are how users talk to your app. React gives you two styles — controlled with state, or uncontrolled with FormData — and both are worth knowing.

Intermediate 5 min Lesson 12 of 42Examples target React 19 with Vite.
Before you start:Event HandlinguseState
0/42 in React
On this page · 5 min · Intermediate

Learning objectives#

  • Handle many fields with one state object
  • Work with text, select, checkbox and radio inputs
  • Submit with onSubmit and reset the form
  • Read uncontrolled forms with FormData

Explanation#

Controlled forms keep every field in state. For many fields, use one object and a single change handler that reads e.target.name.

Inputs differ: text, textarea and select use value; checkboxes use checked; radios use checked={value === option}.

Submit on the form: <form onSubmit={handleSubmit}> and call e.preventDefault(). Disable the button while submitting.

Uncontrolled forms let the browser keep the values; on submit read them with new FormData(e.currentTarget) and Object.fromEntries(...). Less code when you do not need live validation.

Always label inputs (<label htmlFor> or wrapping label) for accessibility, and give each field a name.

Syntax#

JSX Illustrative
const [form, setForm] = useState({ name: '', size: 'M' });
const handleChange = e => setForm({ ...form, [e.target.name]: e.target.value });
<input name="name" value={form.name} onChange={handleChange} />

Code example: A T-shirt order form with one state object#

JSXOrderForm.jsx Runnable
import { useState } from 'react';


const EMPTY = { name: '', colour: 'Black', size: 'M', giftWrap: false, note: '' };


export default function OrderForm() {
  const [form, setForm] = useState(EMPTY);
  const [submitted, setSubmitted] = useState(null);


  function handleChange(e) {
    const { name, type, value, checked } = e.target;
    setForm(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value }));
  }


  function handleSubmit(e) {
    e.preventDefault();
    setSubmitted(form);
    setForm(EMPTY);
  }


  return (
    <form onSubmit={handleSubmit}>
      <label>Name <input name="name" value={form.name} onChange={handleChange} required /></label>


      <label>Colour
        <select name="colour" value={form.colour} onChange={handleChange}>
          <option>White</option><option>Black</option><option>Blue</option>
        </select>
      </label>


      <fieldset>
        <legend>Size</legend>
        {['S', 'M', 'L'].map(size => (
          <label key={size}>
            <input type="radio" name="size" value={size} checked={form.size === size} onChange={handleChange} /> {size}
          </label>
        ))}
      </fieldset>


      <label><input type="checkbox" name="giftWrap" checked={form.giftWrap} onChange={handleChange} /> Gift wrap</label>
      <label>Note <textarea name="note" value={form.note} onChange={handleChange} /></label>


      <button type="submit">Order</button>
      {submitted && <p>Ordered: {submitted.size} {submitted.colour} for {submitted.name}{submitted.giftWrap ? ' 🎁' : ''}</p>}
    </form>
  );
}
Expected output
Fill in “Asha”, choose Blue, size L and gift wrap, then click Order: the form resets and shows “Ordered: L Blue for Asha 🎁”. Every field is controlled by the single form object.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. One state object holds every field; EMPTY is the reset value.
  2. handleChange uses the input’s name to update the matching key — one handler for all fields.
  3. Checkboxes read checked; everything else reads value.
  4. Radios are checked when form.size equals their value.
  5. handleSubmit prevents the reload, stores the order and resets the form.

Real-world use case#

A checkout address form with ten fields uses one state object and one change handler, then posts the object as JSON to the API.

Common mistakes#

value without onChange

A controlled input with value but no onChange is read-only; React warns about it. Add onChange or use defaultValue.

Using value for checkboxes

Checkboxes use checked={bool}, and their change handler reads e.target.checked.

Losing other fields when updating

setForm({ [name]: value }) replaces the whole object. Spread the previous state first.

Best practices#

  • One state object + computed property names
  • Label every input
  • Handle submit on the form
  • Use FormData for simple, uncontrolled forms

Remember this#

text/select/textarea → value. checkbox → checked. radio → checked={v === option}.

{ ...prev, [name]: value } updates one field.

onSubmit + preventDefault.

Practice exercise#

Your task: Write an uncontrolled contact form (name, email, message) that, on submit, reads the values with FormData and shows "Thanks, <name>!".

Need a hint?
Object.fromEntries(new FormData(e.currentTarget)).
Show solution
JSX Runnable
import { useState } from 'react';


export default function ContactForm() {
  const [thanks, setThanks] = useState('');


  function handleSubmit(e) {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(e.currentTarget));
    setThanks(`Thanks, ${data.name}!`);
    e.currentTarget.reset();
  }


  return (
    <form onSubmit={handleSubmit}>
      <input name="name" aria-label="Name" required />
      <input name="email" type="email" aria-label="Email" required />
      <textarea name="message" aria-label="Message" />
      <button>Send</button>
      {thanks && <p>{thanks}</p>}
    </form>
  );
}

Mini quiz#

Forms quizMultiple choice Question 1 of 3

Which property holds a checkbox’s state in its change handler?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.