Chapter 10 of 20 · React for Beginners — Complete Free Tutorial
Forms and Controlled Components
Forms are where the React for Beginners series joins data from the user with state you can trust. React's approach has a name: a controlled component is one whose inputs are driven by state — the form field displays what state says, and every keystroke updates that state. This chapter builds controlled inputs, handles submission, and shows the multi-field patterns real apps need.
Controlled vs uncontrolled
Two ways to read input exist, and knowing both makes React's choice obvious:
| Controlled | Uncontrolled | |
|---|---|---|
| Source of truth | React state | The DOM element |
| Reading the value | value from state | ref or FormData on submit |
| Instant validation | Yes — state changes drive UI | Limited |
| Best for | Live feedback, dependent fields, editing | Simple one-shot forms |
Controlled is the idiomatic React path, so that is what this chapter builds.
One controlled input
Three parts: state for the value, value binding on the input, and onChange that writes each keystroke back. Together they form a loop — state sets the input, the input sets state:
import { useState } from 'react';
function NameField() {
const [name, setName] = useState('');
return (
<label>
Your name
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Ada"
/>
</label>
);
}
Expected output:
typing A → input shows "A"
typing Ad → input shows "Ad"
clearing → input shows "" (empty, not missing)
Because value comes from state, the field can never drift: what you see is always name. The event handler reads e.target.value — the same property you used with plain DOM inputs.
Handling submission
A submit handler must prevent the browser's default reload, then read state — the DOM has already agreed with it:
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
function handleSubmit(event) {
event.preventDefault();
console.log('login attempt', { email, password });
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email}
onChange={(e) => setEmail(e.target.value)} />
<input type="password" value={password}
onChange={(e) => setPassword(e.target.value)} />
<button type="submit">Sign in</button>
</form>
);
}
Expected output: submitting prints an object like login attempt { email: 'ada@example.com', password: '…' } and the page does not reload. Built-in attributes such as type="email" and required still provide the browser's first-line validation.
Many fields, one handler
Instead of a setter per field, a single object state plus the input's name attribute scales cleanly. Spread the previous object and replace the key named by the field:
const [form, setForm] = useState({ username: '', plan: 'free' });
function updateField(event) {
const { name, value } = event.target;
setForm((prev) => ({ ...prev, [name]: value }));
}
// <input name="username" onChange={updateField} />
// <select name="plan" onChange={updateField}> … </select>
Expected output: typing in either field updates only its own key — { username: 'ada', plan: 'free' } — because computed property names and spread keep untouched fields intact. Add fields without adding functions.
Checked, select and textarea
Each control has its own pairing. Checkboxes bind checked to a boolean (no value juggling); selects bind value just like text inputs; textareas bind their text:
const [agree, setAgree] = useState(false);
const [topic, setTopic] = useState('react');
<input type="checkbox" checked={agree}
onChange={(e) => setAgree(e.target.checked)} />
<select value={topic} onChange={(e) => setTopic(e.target.value)}>
<option value="react">React</option>
<option value="vite">Vite</option>
</select>
Expected output: the checkbox reflects the boolean exactly (including being unchecked when false), and the select shows whichever option matches the state string.
Validation as derived UI
With values in state, validation is just conditional rendering from chapter 8 — no extra machinery:
const emailOk = /.+@.+\..+/.test(email);
{email.length > 0 && !emailOk && (
<p className="error">Enter a valid email address.</p>
)}
Expected output: an empty field shows nothing, an invalid address shows the red hint live as you type, and a valid one clears it. Server-side validation still matters — this is friendly feedback, not security.
Common mistakes
- Forgetting
preventDefault()— the page reloads on submit and wipes your state. - Controlling an input without
onChange— React warns and the field becomes read-only; you can never type in it. - Binding
valuebut reading the DOM — trust state in the handler; the DOM copy is a mirror. - Using
valueon checkboxes — checkboxes needcheckedwith a boolean frome.target.checked. - One giant state for unrelated fields — either one object updated by key, or separate states; never a mix that forces you to rewrite everything on each keystroke.
Practice checklist
- Build the name field and confirm the expected outputs while typing and deleting.
- Create the login form; verify submission logs state and does not reload.
- Convert two single states into one object state with
updateField. - Add a checkbox and a select bound to state, and display both values in a preview paragraph.
- Show a live validation message that only appears once you have typed something invalid.
Forms collect one component's data — but what happens when two components need the same state? Next: Sharing State by Lifting It Up.
Frequently Asked Questions
React is a JavaScript library for building user interfaces from reusable components. It manages how your UI updates when data changes, so you describe what the screen should look like and React performs the efficient DOM updates. It is one of the most in-demand frontend skills and powers products from Netflix to Instagram, which makes it an excellent next step after core JavaScript.
You should be comfortable with functions, arrays and objects (map, filter, spread), destructuring, modules and basic async/await. React itself is written in plain JavaScript — JSX is a syntax layer on top. If any of those feel shaky, revisit the JavaScript fundamentals first and this series will go much faster.
React is a library focused only on the UI layer — you choose your own router, data layer and tooling. Angular is a full framework that ships routing, forms, HTTP client and testing conventions as one opinionated package. React's flexibility and huge ecosystem make it the common choice for teams that want to compose their own stack.
A component is a reusable function that returns UI. It receives input through props, keeps private data in state, and can render other components. Pages are composed by nesting small components (navbar, card list, footer) instead of one giant file, which keeps code testable and easy to change.
JSX is a syntax extension that looks like HTML but compiles to React's create() calls — it is a description of UI, not markup the browser parses. Small differences matter: class becomes className, style takes an object, and you embed JavaScript expressions inside curly braces. Once compiled, React turns that description into real DOM nodes.
Props are inputs passed from a parent — they are read-only inside the child, like function arguments. State is private, mutable data owned by a component; updating it triggers a re-render. The rule of thumb: values that come from outside the component are props; values that change over the component's lifetime are state.
Rendering means calling your component function to produce a description of the UI for the current state and props. It does not always mean DOM work — React compares the new description with the previous one (reconciliation) and applies only the differences to the real DOM. This is why updating state 100 times does not mean 100 full page rebuilds.
An effect runs after the component has committed to the screen — after render, not during it. You list dependencies in an array, and React re-runs the effect only when one of them changes. If the effect returns a cleanup function, React runs it before the next re-run and on unmount, which is where you cancel timers, abort fetches and remove listeners.
Use Vite. Create React App is effectively in maintenance mode, while Vite starts instantly (native ES modules) and builds fast, and it is the default choice for new React projects. This series uses Vite for every example — you only need Node.js installed to follow along.
React apps compile to static files (HTML, CSS and JavaScript), so any static host works: Netlify, Vercel, GitHub Pages or Cloudflare Pages. Run the production build, upload the dist folder, and configure a single-page-application fallback so unknown paths load index.html — routing chapters explain why that fallback is required.