Unit 4 - Notes
INT252
Unit 4: Working with Forms
In modern single-page applications (SPAs), forms are the primary mechanism for user interaction and data collection. Unlike traditional HTML forms, React introduces a specific paradigm for managing form data, focusing on state management and event handling to create dynamic, interactive user interfaces.
1. Adding Forms in React
While React forms use standard HTML elements (<form>, <input>, <label>, <button>), the syntax requires JSX compliance.
Key Syntax Differences from HTML
htmlForinstead offor: To associate a label with an input, usehtmlFor(sinceforis a reserved JavaScript keyword).- Self-Closing Tags: All input tags must be self-closed (e.g.,
<input />not<input>). - CamelCase Attributes: Attributes usually follow camelCase (e.g.,
tabIndex,autoComplete).
Basic Structure
function SimpleForm() {
return (
<form>
<label htmlFor="username">Username:</label>
<input type="text" id="username" name="username" />
<button type="submit">Register</button>
</form>
);
}
2. Controlled vs. Uncontrolled Components
React offers two distinct approaches to handling form data. Understanding the distinction is critical for architectural decisions.
A. Controlled Components (The Recommended Way)
In a controlled component, React state (useState) is the "single source of truth."
- The input's value is set by the React state.
- Changes to the input trigger an event handler that updates the state.
Flow of Data:
User types → onChange fires → setState updates state → Component re-renders → Input shows new state value.
import React, { useState } from 'react';
function ControlledInput() {
const [email, setEmail] = useState('');
const handleChange = (e) => {
setEmail(e.target.value); // Update state immediately
};
return (
<input
type="text"
value={email} // Controlled by state
onChange={handleChange} // Triggers state update
/>
);
}
B. Uncontrolled Components
Uncontrolled components act like traditional HTML forms. The DOM handles the data, not React state. React accesses the values only when necessary (usually on submit) using Refs.
Mechanism:
- Use the
useRefhook to create a reference to the DOM node. - Access
ref.current.valueto get the data.
import React, { useRef } from 'react';
function UncontrolledInput() {
const nameInputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
alert('Name: ' + nameInputRef.current.value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameInputRef} />
<button type="submit">Submit</button>
</form>
);
}
Comparison Table
| Feature | Controlled Component | Uncontrolled Component |
|---|---|---|
| Source of Truth | React State | The DOM |
| Data Access | Available at all times (real-time) | Available only on query/submit |
| Validation | Instant validation possible | Validation typically on submit |
| Code Volume | Higher (requires handlers) | Lower (closer to HTML) |
| Use Case | Complex forms, instant feedback, dynamic inputs | Simple forms, file uploads, integrating non-React libs |
3. Handling Forms (Managing Multiple Inputs)
Handling multiple inputs individually with separate useState hooks is inefficient. The standard pattern involves using a single state object and a generic change handler.
The Computed Property Name Syntax
We use [e.target.name] to dynamically update the specific key in the state object corresponding to the input's name attribute.
import React, { useState } from 'react';
function MultiInputForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
role: 'user'
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prevData => ({
...prevData, // Spread existing state to preserve other fields
[name]: value // Dynamic key assignment
}));
};
return (
<form>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<select name="role" value={formData.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
</form>
);
}
4. Submitting Forms
Submitting a form in React involves intercepting the browser's default behavior and processing the data (usually sending it to an API).
Key Steps:
- Attach an
onSubmithandler to the<form>element (not the button). - Prevent Default: Use
event.preventDefault()to stop the browser from reloading the page (standard HTML behavior). - Process data or call an API.
const handleSubmit = async (e) => {
e.preventDefault(); // Crucial step
try {
// Simulation of API call
console.log("Submitting data:", formData);
await submitToAPI(formData);
// Optional: Reset form after success
setFormData({ username: '', email: '' });
} catch (error) {
console.error("Submission failed");
}
};
// In JSX:
// <form onSubmit={handleSubmit}> ...
5. Form Validation
Validation ensures the data collected is clean, secure, and usable. Validation usually happens in two places:
- Client-side (React): For user experience (instant feedback).
- Server-side: For security (never trust the client).
We focus here on Client-side validation.
Validation Logic
Create a function that checks the formData against rules (regex, length checks, required fields) and returns an object containing error messages.
const validate = (values) => {
const errors = {};
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i; // Simple email regex
if (!values.username) {
errors.username = "Username is required!";
}
if (!values.email) {
errors.email = "Email is required!";
} else if (!regex.test(values.email)) {
errors.email = "This is not a valid email format!";
}
if (values.password.length < 4) {
errors.password = "Password must be more than 4 characters";
}
return errors;
};
6. Error Checking and Displaying Errors
Displaying errors involves managing an errors state object and conditionally rendering messages in the UI.
Implementation Steps
- State for Errors: Create a state variable
const [formErrors, setFormErrors] = useState({}). - Trigger Validation: Run validation inside
handleSubmitoruseEffectdependent onformData. - Blocking Submission: If
Object.keys(errors).length > 0, do not submit. - Conditional Rendering: Display the error message next to the input field.
Comprehensive Example: Full Flow
Below is a complete example integrating Handling, Validation, Error Display, and Submission.
import React, { useState, useEffect } from "react";
function RegistrationForm() {
const initialValues = { username: "", email: "", password: "" };
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const [isSubmit, setIsSubmit] = useState(false);
// 1. Handle Input Change
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
// 2. Handle Submission
const handleSubmit = (e) => {
e.preventDefault();
const errors = validate(formValues);
setFormErrors(errors);
setIsSubmit(true);
};
// 3. Submit only if no errors
useEffect(() => {
if (Object.keys(formErrors).length === 0 && isSubmit) {
console.log("Form Submitted Successfully:", formValues);
// API Call would go here
}
}, [formErrors, isSubmit, formValues]);
// 4. Validation Logic
const validate = (values) => {
const errors = {};
if (!values.username) {
errors.username = "Username is required";
}
if (!values.email) {
errors.email = "Email is required";
} else if (!values.email.includes("@")) {
errors.email = "Invalid email address";
}
if (!values.password) {
errors.password = "Password is required";
} else if (values.password.length < 6) {
errors.password = "Password must be 6 characters or more";
}
return errors;
};
return (
<div className="container">
<h1>Login Form</h1>
<form onSubmit={handleSubmit}>
{/* Username Field */}
<div className="form-group">
<label>Username</label>
<input
type="text"
name="username"
value={formValues.username}
onChange={handleChange}
// Add red border class if error exists
className={formErrors.username ? "input-error" : ""}
/>
{/* 5. Display Error */}
<p className="error-text">{formErrors.username}</p>
</div>
{/* Email Field */}
<div className="form-group">
<label>Email</label>
<input
type="text"
name="email"
value={formValues.email}
onChange={handleChange}
className={formErrors.email ? "input-error" : ""}
/>
<p className="error-text">{formErrors.email}</p>
</div>
{/* Password Field */}
<div className="form-group">
<label>Password</label>
<input
type="password"
name="password"
value={formValues.password}
onChange={handleChange}
className={formErrors.password ? "input-error" : ""}
/>
<p className="error-text">{formErrors.password}</p>
</div>
<button type="submit">Submit</button>
</form>
</div>
);
}
export default RegistrationForm;
Specific Input Nuances
- Textarea: In HTML,
<textarea>Value</textarea>. In React, it uses avalueattribute:<textarea value={state} />. - Select: Instead of putting
selectedon an<option>, React puts avalueon the root<select>tag. - Checkbox: Uses
checkedattribute (boolean) instead ofvalue. When handling changes, accesse.target.checkedinstead ofe.target.value.
// Checkbox Example
<input
type="checkbox"
checked={isSubscribed}
onChange={(e) => setIsSubscribed(e.target.checked)}
/>