Registration Form
Problem
A registration form needs a confirmed password field (both values must match) and an async email-uniqueness check that queries the server while the user is still typing.
Solution
Registration form with async validation and password confirmation.
typescript
import { createForm } from '@vielzeug/formit';
const registrationForm = createForm({
defaultValues: {
username: '',
email: '',
password: '',
confirmPassword: '',
},
validators: {
username: [
(v) => (!v ? 'Username is required' : undefined),
(v) => (v && String(v).length < 3 ? 'Username must be at least 3 characters' : undefined),
async (v) => {
if (!v) return;
const response = await fetch(`/api/check-username?username=${v}`);
const { exists } = await response.json();
if (exists) return 'Username is already taken';
},
],
email: [
(v) => (!v ? 'Email is required' : undefined),
(v) => (v && !String(v).includes('@') ? 'Invalid email format' : undefined),
],
password: [
(v) => (!v ? 'Password is required' : undefined),
(v) => (v && String(v).length < 8 ? 'Min 8 characters' : undefined),
(v) => (v && !/[A-Z]/.test(String(v)) ? 'Must contain uppercase letter' : undefined),
(v) => (v && !/[0-9]/.test(String(v)) ? 'Must contain a number' : undefined),
],
},
validator: (values) => {
const errors: Record<string, string> = {};
if (values['password'] !== values['confirmPassword']) {
errors['confirmPassword'] = 'Passwords must match';
}
return errors;
},
});
// Submit
async function handleRegistration() {
const result = await registrationForm.submit(async (values) => {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
});
return response.json();
});
if (!result.ok) {
console.log(result.errors);
return;
}
}Pitfalls
- Async validators run on every
validateField()call, including on every blur. Debounce them to avoid an API request on each keystroke. - The password-confirmation validator must re-run when the original password field changes, not only when confirmation changes. Read
form.values.passwordinside the confirmation validator. form.state.isSubmittingstaystrueuntil the submit handler resolves or rejects. Unhandled rejections inside the handler leave the form stuck in the submitting state.