Forms in React
Forms are one of the most important parts of any web application. They allow users to enter information — signing up, logging in, searching, filling out contact details, and more. React provides a structured way to manage form input using a pattern called controlled components.
In a controlled component, React state is the single source of truth for all form values. The input field's value is always driven by state, and state is always updated through event handlers.
Controlled vs Uncontrolled Components
There are two ways to handle form inputs in React:
- Controlled components — The input value is stored in React state. React controls what the input shows at all times. This is the recommended approach.
- Uncontrolled components — The input manages its own value internally, and React reads it only when needed (using
useRef). This is less common and harder to manage.
This topic focuses on controlled components, which are the standard in React development.
A Simple Text Input (Controlled)
import { useState } from 'react';
function NameForm() {
const [name, setName] = useState("");
function handleChange(event) {
setName(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
alert(`Submitted name: ${name}`);
}
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={name} onChange={handleChange} />
</label>
<button type="submit">Submit</button>
</form>
);
}
Here, the input's value is tied to the name state. Every keystroke calls handleChange, which updates the state. The input always displays whatever is in the state.
Handling Multiple Inputs
When a form has multiple fields, each field can have its own state variable, or a single state object can manage all values at once. Using one state object is often cleaner for forms with several fields:
import { useState } from 'react';
function RegistrationForm() {
const [formData, setFormData] = useState({
username: "",
email: "",
password: "",
});
function handleChange(event) {
const { name, value } = event.target;
setFormData({ ...formData, [name]: value });
}
function handleSubmit(event) {
event.preventDefault();
console.log("Form submitted:", formData);
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</div>
<button type="submit">Register</button>
</form>
);
}
The key trick here is using name attributes on each input that match the keys in the state object. When any input changes, handleChange reads the input's name attribute to know which field to update, using computed property syntax: [name]: value.
Handling Textarea
In HTML, a textarea has its value between its tags. In React, it behaves like a text input — with a value prop:
import { useState } from 'react';
function FeedbackForm() {
const [message, setMessage] = useState("");
return (
<div>
<label>Feedback:</label>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
rows={4}
/>
<p>Characters typed: {message.length}</p>
</div>
);
}
Handling Select Dropdowns
Select elements work the same as text inputs — controlled via value and onChange:
import { useState } from 'react';
function CountrySelector() {
const [country, setCountry] = useState("us");
return (
<div>
<label>Country:</label>
<select value={country} onChange={(e) => setCountry(e.target.value)}>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="ca">Canada</option>
<option value="au">Australia</option>
</select>
<p>Selected: {country}</p>
</div>
);
}
Handling Checkboxes
Checkboxes use the checked prop instead of value:
import { useState } from 'react';
function TermsCheckbox() {
const [agreed, setAgreed] = useState(false);
return (
<div>
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
I agree to the terms and conditions
</label>
{agreed && <p>Thank you for agreeing!</p>}
</div>
);
}
Note the use of e.target.checked (not e.target.value) for checkboxes, since a checkbox's state is a boolean — either checked or not.
Basic Form Validation
Validation can be added before processing the form submission:
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState("");
const [error, setError] = useState("");
function handleSubmit(event) {
event.preventDefault();
if (!email.includes("@")) {
setError("Please enter a valid email address.");
return;
}
setError("");
alert(`Logging in with: ${email}`);
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter your email"
/>
{error && <p>{error}</p>}
<button type="submit">Log In</button>
</form>
);
}
Key Points
- Controlled components link form input values to React state via the
valueprop. - Every input change calls an
onChangehandler that updates state. - Use
event.preventDefault()in form submit handlers to stop page reloads. - Multiple form fields can be managed with a single state object using the input's
nameattribute. - Checkboxes use
checkedande.target.checked, while other inputs usevalueande.target.value. - Basic validation can be added inside the submit handler before processing data.
The next topic covers the useEffect Hook — one of the most important Hooks in React, used to handle side effects like fetching data, timers, and subscriptions.
