Controlled components give you full control over form state and make validation straightforward.
import { useState } from 'react';
const initialState = { name: '', email: '', message: '' };
function ContactForm() {
const [fields, setFields] = useState(initialState);
const [errors, setErrors] = useState({});
const [success, setSuccess] = useState(false);
function validate(data) {
const errs = {};
if (!data.name.trim())
errs.name = 'Name is required.';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email))
errs.email = 'Enter a valid email address.';
if (data.message.trim().length < 10)
errs.message = 'Message must be at least 10 characters.';
return errs;
}
function handleChange(e) {
setFields(prev => ({ ...prev, [e.target.name]: e.target.value }));
}
async function handleSubmit(e) {
e.preventDefault();
const errs = validate(fields);
if (Object.keys(errs).length) { setErrors(errs); return; }
await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(fields),
});
setFields(initialState);
setErrors({});
setSuccess(true);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={fields.name} onChange={handleChange} placeholder="Name" />
{errors.name && <p className="error">{errors.name}</p>}
<input name="email" value={fields.email} onChange={handleChange} placeholder="Email" />
{errors.email && <p className="error">{errors.email}</p>}
<textarea name="message" value={fields.message} onChange={handleChange} />
{errors.message && <p className="error">{errors.message}</p>}
<button type="submit">Send</button>
{success && <p>Message sent!</p>}
</form>
);
}
All Comments