Forms are where most real React work happens: login screens, signup pages, search boxes, checkout flows. React handles forms differently from plain HTML, and the core idea is the controlled component — an input whose value is owned by React state rather than by the browser. Once this clicks, form handling becomes one of the most predictable parts of React.
This guide builds up from a single controlled input to a complete multi-field form with validation and submission. It assumes you are comfortable with useState; if not, the React hooks introduction is the prerequisite.
The two-way binding idea
In plain HTML, an <input> manages its own value internally — you type, and the DOM remembers it. In React, you deliberately take that control away from the DOM and give it to state. The pattern has two halves that must always go together:
- Set the input's
valuefrom state, so the input displays whatever state says. - Update state in an
onChangehandler, so typing changes the state.
This creates a tight loop: state drives the input, and the input drives state. React becomes the single source of truth for what is in the field.
import { useState } from "react";
function NameField() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Your name"
/>
<p>You typed: {name}</p>
</div>
);
}
export default NameField;
Every keystroke fires onChange, updates name, and re-renders. Because value={name}, the input always shows the current state. The live <p> proves state and input are perfectly in sync.
Common mistake: Setting
valuewithout anonChange. React then locks the input as read-only, because you have told it the value comes from state but given it no way to change that state. Always pair them, or usedefaultValueif you truly want the DOM to manage the field.
Controlled vs uncontrolled components
There are two philosophies for form inputs in React, and interviewers frequently ask you to compare them.
| Controlled | Uncontrolled | |
|---|---|---|
| Value stored in | React state | The DOM |
| Read the value | Directly from state | With a ref |
| Update on change | onChange handler |
Not tracked |
| Best for | Most forms, validation | Simple or file inputs |
A controlled component keeps the value in state, giving you instant access for validation and conditional logic. An uncontrolled component lets the DOM hold the value and you grab it later with a ref — useful for very simple forms or the <input type="file"> element, which must be uncontrolled. For nearly everything else, controlled is the recommended default.
Handling multiple fields with one handler
Real forms have many inputs. Writing a separate handler for each gets tedious fast. The clean solution: store all fields in one state object, give each input a name matching a key, and use a single handler that updates by name.
import { useState } from "react";
function SignupForm() {
const [form, setForm] = useState({ email: "", city: "" });
function handleChange(e) {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
}
return (
<form>
<input name="email" value={form.email} onChange={handleChange} />
<input name="city" value={form.city} onChange={handleChange} />
</form>
);
}
export default SignupForm;
Two details make this work. The [name]: value computed property updates whichever field changed. And the { ...prev, ... } spread copies the other fields so you do not wipe them — remember, you must never mutate state directly, a rule explained further in the state management overview.
Other input types
Checkboxes, selects, and text areas are all controlled the same way, with a couple of tweaks. A checkbox uses checked instead of value and reads e.target.checked:
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
A <select> is controlled by putting value on the select element itself, not on the options. A <textarea> in React also uses a value prop rather than putting text between tags as HTML does. Once you internalize "value plus onChange", every input type follows the same shape.
Submitting the form
To handle submission, put an onSubmit on the <form> and call preventDefault to stop the browser's default page reload. Then you have all the data sitting in state, ready to send to an API or validate.
import { useState } from "react";
function ContactForm() {
const [email, setEmail] = useState("");
function handleSubmit(e) {
e.preventDefault();
console.log("Submitting:", email);
// send to an API here
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<button type="submit">Send</button>
</form>
);
}
export default ContactForm;
The e.preventDefault() is essential. Without it, submitting reloads the page and destroys your React state, which is almost never what you want in a single-page app. After a successful submit you often redirect the user, which pairs with the useNavigate hook from the React Router tutorial.
Adding validation
Because the values live in state, validation is just reading that state and deriving an error message. You can validate live as the user types or on submit.
import { useState } from "react";
function EmailField() {
const [email, setEmail] = useState("");
const isValid = email.includes("@") && email.includes(".");
return (
<div>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
{email && !isValid && <p>Please enter a valid email.</p>}
<button disabled={!isValid}>Continue</button>
</div>
);
}
export default EmailField;
Notice how validation becomes trivial: you compute isValid from state, show an error conditionally, and even disable the button. This immediate, expression-driven validation is exactly why controlled components are worth the small extra code, and it uses the conditional rendering patterns from the JSX syntax guide.
Pro tip: For small forms, plain controlled components plus a little validation logic are perfect and worth learning first. For large forms with many rules, teams often reach for a library like React Hook Form to reduce boilerplate — but do not start there. Understanding controlled components manually is what makes those libraries make sense later.
Resetting and pre-filling forms
Because state is the single source of truth, resetting a form is just resetting its state. After a successful submit, set each field back to its initial value and the inputs clear themselves — no DOM manipulation required.
function handleSubmit(e) {
e.preventDefault();
saveContact(form);
setForm({ email: "", city: "" }); // clears the inputs
}
Pre-filling an edit form works the same way in reverse: initialize state with the existing record. If the data arrives from an API after the component mounts, load it in a useEffect and call the setter once it lands. This "state in, state out" symmetry is one of the quiet advantages of controlled components — the same mental model handles empty forms, edit forms, and reset forms without special cases.
Interview note: A common question is "Why does React recommend controlled components?" A strong answer names three things: React state stays the single source of truth, validation and conditional UI become trivial expressions over that state, and resetting or pre-filling is just setting state. Then acknowledge that uncontrolled inputs with refs are the right choice for file inputs and very simple forms.
Common pitfalls to avoid
A few mistakes trip up nearly every beginner. Initializing controlled inputs with undefined instead of an empty string causes React to treat them as uncontrolled and warn you — always start text fields at "". Forgetting the spread when updating an object in state silently drops your other fields. And mutating state arrays or objects directly, then calling the setter with the same reference, often fails to re-render at all.
One more subtle trap: performing heavy work inside the onChange handler. Because it fires on every keystroke, expensive operations like filtering a large list on each change can make typing feel sluggish. When you notice lag, debounce the work or derive it separately rather than running it inline for every character. For most forms this never becomes an issue, but it is worth knowing why a fast-typing user might see a laggy field.
Where to go next
Controlled components are the backbone of every serious React form, so practice by building a real one: a signup form with email, password, a terms checkbox, live validation, and a submit that logs the data. That single exercise exercises state, events, conditional rendering, and submission all at once.
From here, connect your form to a backend and a router to build genuine features — a login flow that validates, submits, and redirects. To see forms inside a complete first project, work through the beginner React tutorial, and revisit the React learning hub for the guided path that places forms alongside hooks, routing, and state management.
Frequently Asked Questions
What is a controlled component in React?
What is the difference between controlled and uncontrolled components?
Why does my input become read-only when I set value?
How do I handle multiple inputs without writing many handlers?
How do I stop a React form from reloading the page on submit?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

