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.
On this page · 5 min · Intermediate
On this page
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
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#
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#
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>
);
}
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#
- One state object holds every field; EMPTY is the reset value.
- handleChange uses the input’s name to update the matching key — one handler for all fields.
- Checkboxes read checked; everything else reads value.
- Radios are checked when form.size equals their value.
- 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#
A controlled input with value but no onChange is read-only; React warns about it. Add onChange or use defaultValue.
Checkboxes use checked={bool}, and their change handler reads e.target.checked.
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?
Show solution
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#
Which property holds a checkbox’s state in its change handler?
Want more? Practise all React MCQs · React interview questions
My notes#
Saved in this browser.