React Forms
Controlled vs Uncontrolled Components
| Feature | Controlled | Uncontrolled |
|---|---|---|
| Data stored in | React state | DOM (via ref) |
| Access value | state.value | ref.current.value |
| Validation | Easy — on every change | On submit only |
| Recommended | Yes — for most cases | Simple forms, file inputs |
import { useState } from 'react'
function RegistrationForm() {
const [form, setForm] = useState({
username: '',
email: '',
password: '',
role: 'user',
newsletter: false,
bio: '',
country: 'us',
})
const handleChange = (e) => {
const { name, value, type, checked } = e.target
setForm(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}))
}
const handleSubmit = (e) => {
e.preventDefault()
console.log('Submitted:', form)
}
return (
<form onSubmit={handleSubmit}>
{/* Text input */}
<input name="username" value={form.username} onChange={handleChange}
placeholder="Username" required />
{/* Email input */}
<input name="email" type="email" value={form.email} onChange={handleChange}
placeholder="Email" required />
{/* Password input */}
<input name="password" type="password" value={form.password} onChange={handleChange}
placeholder="Password" minLength={8} required />
{/* Radio buttons */}
<label>
<input type="radio" name="role" value="user"
checked={form.role === 'user'} onChange={handleChange} />
User
</label>
<label>
<input type="radio" name="role" value="admin"
checked={form.role === 'admin'} onChange={handleChange} />
Admin
</label>
{/* Checkbox */}
<label>
<input type="checkbox" name="newsletter"
checked={form.newsletter} onChange={handleChange} />
Subscribe to newsletter
</label>
{/* Textarea */}
<textarea name="bio" value={form.bio} onChange={handleChange}
placeholder="Bio" rows={3} />
{/* Select */}
<select name="country" value={form.country} onChange={handleChange}>
<option value="us">United States</option>
<option value="uk">United Kingdom</option>
<option value="in">India</option>
</select>
<button type="submit">Register</button>
<pre>{JSON.stringify(form, null, 2)}</pre>
</form>
)
}
import { useState } from 'react'
function ValidatedForm() {
const [form, setForm] = useState({ email: '', password: '' })
const [errors, setErrors] = useState({})
const [touched, setTouched] = useState({})
const validate = (values) => {
const errs = {}
if (!values.email) errs.email = 'Email is required'
else if (!/\S+@\S+\.\S+/.test(values.email)) errs.email = 'Invalid email'
if (!values.password) errs.password = 'Password is required'
else if (values.password.length < 8) errs.password = 'Min 8 characters'
return errs
}
const handleChange = (e) => {
const { name, value } = e.target
const newForm = { ...form, [name]: value }
setForm(newForm)
if (touched[name]) {
setErrors(validate(newForm))
}
}
const handleBlur = (e) => {
const { name } = e.target
setTouched(prev => ({ ...prev, [name]: true }))
setErrors(validate(form))
}
const handleSubmit = (e) => {
e.preventDefault()
const allTouched = { email: true, password: true }
setTouched(allTouched)
const errs = validate(form)
setErrors(errs)
if (Object.keys(errs).length === 0) {
alert('Form submitted successfully!')
}
}
return (
<form onSubmit={handleSubmit}>
<div>
<input name="email" type="email" value={form.email}
onChange={handleChange} onBlur={handleBlur}
placeholder="Email"
className={errors.email && touched.email ? 'input-error' : ''} />
{errors.email && touched.email &&
<span className="error">{errors.email}</span>}
</div>
<div>
<input name="password" type="password" value={form.password}
onChange={handleChange} onBlur={handleBlur}
placeholder="Password"
className={errors.password && touched.password ? 'input-error' : ''} />
{errors.password && touched.password &&
<span className="error">{errors.password}</span>}
</div>
<button type="submit">Login</button>
</form>
)
}
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.