Forms
advancedPart of Advanced React
Theory
Forms are a fundamental part of most web applications. In React, forms can be built using either controlled components (where form data is handled by React state) or uncontrolled components (where the DOM handles the data).
Controlled Components
In a controlled component, React state is the single source of truth for the form input. The input's value is bound to state, and changes are handled via onChange:
function SimpleForm() {
const [name, setName] = useState('')
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
)
}Every keystroke calls setName, which updates state and re-renders the input with the new value.
Form Input Types
Text Input
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>Textarea
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
/>Select / Dropdown
<select value={selectedCity} onChange={(e) => setSelectedCity(e.target.value)}>
<option value="">Choose a city</option>
<option value="nyc">New York</option>
<option value="london">London</option>
</select>Checkbox
<input
type="checkbox"
checked={isAgreed}
onChange={(e) => setIsAgreed(e.target.checked)}
/>Radio Buttons
<label>
<input
type="radio"
value="male"
checked={gender === 'male'}
onChange={(e) => setGender(e.target.value)}
/>
Male
</label>
<label>
<input
type="radio"
value="female"
checked={gender === 'female'}
onChange={(e) => setGender(e.target.value)}
/>
Female
</label>Form Submission
Form submission is handled with the onSubmit event on the <form> element. Always call e.preventDefault() to prevent the default browser page reload:
function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
console.log('Logging in with:', 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">Login</button>
</form>
)
}Form Validation
Client-side validation in React is typically done in the handleSubmit function or as the user types:
function validateForm(values) {
const errors = {}
if (!values.email) {
errors.email = 'Email is required'
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Email is invalid'
}
if (!values.password) {
errors.password = 'Password is required'
} else if (values.password.length < 6) {
errors.password = 'Password must be at least 6 characters'
}
return errors
}
function SignupForm() {
const [values, setValues] = useState({ email: '', password: '' })
const [errors, setErrors] = useState({})
const [submitted, setSubmitted] = useState(false)
const handleChange = (e) => {
const { name, value } = e.target
setValues({ ...values, [name]: value })
}
const handleSubmit = (e) => {
e.preventDefault()
const validationErrors = validateForm(values)
setErrors(validationErrors)
setSubmitted(true)
if (Object.keys(validationErrors).length === 0) {
console.log('Form submitted:', values)
}
}
return (
<form onSubmit={handleSubmit}>
<input name="email" value={values.email} onChange={handleChange} />
{errors.email && <span className="error">{errors.email}</span>}
<input name="password" type="password" value={values.password} onChange={handleChange} />
{errors.password && <span className="error">{errors.password}</span>}
<button type="submit">Sign Up</button>
</form>
)
}useRef for Uncontrolled Inputs
Sometimes you don't need to control an input with state. Uncontrolled components let the DOM handle the input value, and you access it via useRef when needed:
function UncontrolledForm() {
const nameRef = useRef(null)
const emailRef = useRef(null)
const handleSubmit = (e) => {
e.preventDefault()
console.log({
name: nameRef.current.value,
email: emailRef.current.value,
})
}
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} type="text" defaultValue="John" />
<input ref={emailRef} type="email" />
<button type="submit">Submit</button>
</form>
)
}Use uncontrolled components when:
- You only need the value at submission time
- You're integrating with non-React libraries
- You want simpler code for simple forms
Form Libraries
For complex forms with many fields, validation, and dynamic behaviors, consider:
- React Hook Form — performant, minimal re-renders, easy validation
- Formik — popular, feature-rich form library
- React Final Form — subscription-based, good performance
React Hook Form example:
import { useForm } from 'react-hook-form'
function MyForm() {
const { register, handleSubmit, errors } = useForm()
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('email', { required: true })} />
{errors.email && <span>Required</span>}
<button type="submit">Submit</button>
</form>
)
}Start with controlled components — they're the most React-idiomatic. Switch to uncontrolled or a form library only when you encounter performance issues or the form becomes overly complex.
Practical Examples
The noValidate attribute on the form element disables the browser's default validation, allowing you to show your own custom validation messages.
Exercises
Build a Feedback Form
Create a FeedbackForm component with controlled inputs: name (text), rating (select 1-5), comment (textarea), and subscribe (checkbox). On submit, log the form data to the console.
Expected Output:
A working form that logs an object with name, rating, comment, and subscribe values on submitForm Validation
Create a login form with email and password fields. Validate that email is not empty and has a valid format, and password is at least 8 characters. Show inline error messages below each field.
Expected Output:
Submitting with empty fields shows error messages. Submitting with valid fields logs the data. Errors clear when the user types valid input.Dynamic Input Fields
Create a dynamic form where users can add and remove email input fields. Start with one email field. Provide an 'Add Email' button that adds another field and a 'Remove' button next to each field (except the last one). All values should be controlled by React state.
Expected Output:
A dynamic form where users can add/remove email fields. Submit logs the array of email values.Mini Quiz
Mini Quiz
Mini Project
Mini Project: Multi-Step Signup Form
Build a multi-step registration form with three steps: Personal Info (name, email), Account Details (username, password), and Review (summary of all data). Users can navigate forward and backward between steps. Validate each step before allowing the next step.
Requirements:
Bonus Challenge
Add animated transitions between steps. Also add a 'Save as Draft' button that persists the current data to localStorage.