Form Validation
Validate input and show helpful error messages with state.
With controlled inputs you can validate as the user types or on submit. Store any error messages in state and render them next to the field.
A simple approach
- Keep field values in state.
- On submit (or change), run your checks.
- Store errors in state and display them conditionally.
Always pair custom validation with proper HTML attributes like required and type="email" for accessibility and native support.
Example
import { useState } from 'react';
function SignupForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
function handleSubmit(e) {
e.preventDefault();
if (!email.includes('@')) {
setError('Please enter a valid email.');
return;
}
setError('');
console.log('Signed up', email);
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
{error && <p>{error}</p>}
<button>Sign up</button>
</form>
);
}When to use it
- A registration form validates the email field on blur and displays an inline error message under the field so the user gets feedback before hitting submit.
- A password strength meter runs validation logic inside an onChange handler and shows a live strength bar without waiting for the form to be submitted.
- A checkout form validates all fields on submit and focuses the first invalid input automatically to help keyboard-only users correct mistakes quickly.
More examples
Validate on submit
Validates the email on submit, stores the error message in state, and renders it conditionally below the input.
import { useState } from 'react';
function SignupForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
function handleSubmit(e) {
e.preventDefault();
if (!email.includes('@')) {
setError('Please enter a valid email address.');
return;
}
setError('');
console.log('Submitting:', email);
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={e => setEmail(e.target.value)} type="email" />
{error && <p className="error">{error}</p>}
<button type="submit">Sign up</button>
</form>
);
}Validate on blur
Runs validation when the user leaves the field (onBlur) so the error appears after they finish typing, not during.
import { useState } from 'react';
function PasswordField() {
const [value, setValue] = useState('');
const [error, setError] = useState('');
const validate = () =>
setError(value.length < 8 ? 'Minimum 8 characters.' : '');
return (
<div>
<input
type="password"
value={value}
onChange={e => setValue(e.target.value)}
onBlur={validate}
/>
{error && <span className="error">{error}</span>}
</div>
);
}Multi-field form validation
Validates multiple fields in a single validate function and stores all errors in one state object.
import { useState } from 'react';
function ContactForm() {
const [fields, setFields] = useState({ name: '', email: '' });
const [errors, setErrors] = useState({});
function validate() {
const e = {};
if (!fields.name.trim()) e.name = 'Name is required.';
if (!fields.email.includes('@')) e.email = 'Invalid email.';
return e;
}
function handleSubmit(evt) {
evt.preventDefault();
const e = validate();
if (Object.keys(e).length) { setErrors(e); return; }
console.log('Valid:', fields);
}
const update = key => e => setFields(f => ({ ...f, [key]: e.target.value }));
return (
<form onSubmit={handleSubmit}>
<input value={fields.name} onChange={update('name')} placeholder="Name" />
{errors.name && <p className="error">{errors.name}</p>}
<input value={fields.email} onChange={update('email')} placeholder="Email" />
{errors.email && <p className="error">{errors.email}</p>}
<button type="submit">Send</button>
</form>
);
}
Discussion