React forms capture user data through inputs, textareas, checkboxes, and select menus. Unlike plain HTML forms that let the browser store field values internally, React lets you choose between controlled inputs driven by state or uncontrolled inputs read via FormData on submit.
In a controlled input, React state is the single source of truth. Every keystroke fires onChange, updates state, and passes the new value back into value. Grouping fields into one object scales cleanly across multi-field forms.
import { useState } from 'react';
export function ContactForm({ onSuccess }) {
const [form, setForm] = useState({ email: '', topic: 'support', agree: false });
const [error, setError] = useState('');
function handleChange(event) {
const { name, type, value, checked } = event.target;
setForm((prev) => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}));
}
function handleSubmit(event) {
event.preventDefault();
if (!form.email.includes('@')) {
setError('Enter a valid email address.');
return;
}
setError('');
onSuccess(form);
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input id="email" name="email" value={form.email} onChange={handleChange} />
<label htmlFor="topic">Topic</label>
<select id="topic" name="topic" value={form.topic} onChange={handleChange}>
<option value="support">Technical Support</option>
<option value="billing">Billing Question</option>
</select>
<label>
<input type="checkbox" name="agree" checked={form.agree} onChange={handleChange} />
Accept terms
</label>
{error && <p role="alert">{error}</p>}
<button type="submit">Send Message</button>
</form>
);
}
If you only validate on submit and do not need instant live character counters, reading new FormData(event.currentTarget) avoids re-rendering on every keystroke. React 19 also supports passing an async function directly to <form action={submitAction}> with useActionState to track pending and error states automatically.
undefined (useState()) and later setting a string causes React's "uncontrolled input changing to controlled" warning. Always initialize text inputs with ''.value on Checkboxes: Checkboxes use the checked boolean prop rather than value.<textarea> use child text in React?No. In React, <textarea value={text} onChange={...} /> uses a value prop just like <input> instead of placing text between opening and closing tags.
Call event.currentTarget.reset() after your async submit succeeds.
value is set without onChange?Passing value={state} without an onChange handler locks the field as read-only because React continuously overwrites the input with the unchanged state.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With