Handling Events and Forms in React
Event Handling in React
React wraps native browser events in a cross-browser wrapper called a synthetic event. You attach event handlers directly to JSX elements using camelCase event names:
function ClickDemo() {
function handleClick(event) {
console.log("Clicked!", event.type);
}
return <button onClick={handleClick}>Click me</button>;
}
Notice: you pass the function reference (handleClick), not a call (handleClick()). Writing onClick={handleClick()} would call the function immediately on render, not on click.
Common Event Handlers
<button onClick={handleClick}>Click</button>
<input onChange={handleChange} />
<form onSubmit={handleSubmit}>...</form>
<input onFocus={handleFocus} onBlur={handleBlur} />
<div onMouseEnter={handleHover} />
<input onKeyDown={handleKey} />
Controlled Inputs
In React, form inputs are best managed as controlled inputs — their value is bound to state and every change updates that state. This keeps React as the single source of truth for form data.
import { useState } from "react";
function SearchBar() {
const [query, setQuery] = useState("");
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search courses..."
/>
<p>You typed: {query}</p>
</div>
);
}
The pattern is always: value = state, onChange = state setter.
Form Submission
Handle form submission with the onSubmit event. Always call e.preventDefault() to stop the browser from reloading the page:
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
function handleSubmit(e) {
e.preventDefault(); // Prevent page reload
if (!email || !password) {
setError("Both fields are required");
return;
}
setError("");
console.log("Logging in with:", email);
// Send to API...
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={e => setPassword(e.target.value)}
placeholder="Password"
/>
{error && <p className="error">{error}</p>}
<button type="submit">Log In</button>
</form>
);
}
Managing Multiple Fields Efficiently
For forms with many fields, store the form data as a single state object:
function ContactForm() {
const [formData, setFormData] = useState({
name: "",
email: "",
message: ""
});
function handleChange(e) {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
}
function handleSubmit(e) {
e.preventDefault();
console.log("Form data:", formData);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
<input name="email" type="email" value={formData.email} onChange={handleChange} placeholder="Email" />
<textarea name="message" value={formData.message} onChange={handleChange} placeholder="Message" />
<button type="submit">Send</button>
</form>
);
}
The [name]: value pattern uses computed property names to update the correct field based on the input's name attribute.
Form Validation
Basic validation runs inside the submit handler:
const [errors, setErrors] = useState({});
function validate(data) {
const newErrors = {};
if (!data.name.trim()) newErrors.name = "Name is required";
if (!data.email.includes("@")) newErrors.email = "Enter a valid email";
if (data.message.length < 10) newErrors.message = "Message must be at least 10 characters";
return newErrors;
}
function handleSubmit(e) {
e.preventDefault();
const validationErrors = validate(formData);
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
// Submit the form
}
// In JSX:
{errors.name && <p className="error">{errors.name}</p>}
Key Takeaways
- React uses camelCase synthetic event handlers like onClick, onChange, and onSubmit attached directly to JSX
- Always pass a function reference to event handlers — not a function call
- Controlled inputs bind input value to state and update state on every change via onChange
- Always call e.preventDefault() in onSubmit to prevent the browser from reloading the page
- For multi-field forms, use a single state object and update it with computed property names: { ...prev, [name]: value }
- Run validation inside the submit handler and store errors in state to display them inline
Practice Exercise
- Build a contact form with name, email, and message fields as controlled inputs
- Display the current character count for the message field as the user types
- Add validation: name required, email must contain @, message must be at least 20 characters
- Show a success message when the form is successfully submitted (simulate — no API needed)
- Add a reset button that clears all fields and errors
Try it yourself
Key Takeaways
- React event handlers are camelCase (onClick, onChange, onSubmit) and receive a synthetic event object
- Pass function references to event handlers — not function calls: onClick={handleClick}, not onClick={handleClick()}
- Controlled inputs bind value to state and update state via onChange, keeping React as the single source of truth
- Always call e.preventDefault() in onSubmit to prevent the browser from reloading the page
- Use a single state object with computed property names ([name]: value) to handle multiple form fields efficiently
- Run validation inside the submit handler and store errors in state to display them inline near each field
Quick Quiz
1.What is wrong with <button onClick={handleClick()}>Click</button>?
2.What is a controlled input in React?
3.Why is e.preventDefault() called in a form's onSubmit handler?
4.What does the pattern setFormData(prev => ({ ...prev, [name]: value })) do?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx