Step 3: Contact Information Validation
Validating email, phone, and address fields with format validators and conditional rules.
What We're Validatingβ
Step 3 contains contact and address fields:
| Field | Validation Rules |
|---|---|
phoneMain | Required, phone format |
phoneAdditional | Optional, phone format |
email | Required, email format |
emailAdditional | Optional, email format |
registrationAddress.city | Required |
registrationAddress.street | Required |
registrationAddress.house | Required |
registrationAddress.postalCode | Optional, 6 digits |
residenceAddress.* | Required when sameAsRegistration is false |
Creating the Validator Fileβ
Create the validator file for Step 3:
touch src/schemas/validators/contact-info.ts
Implementationβ
Phone and Email Validationβ
Start with phone and email format validation:
import { required, email, phone, pattern, applyWhen } from '@reformer/core/validators';
import type { ValidationSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';
/**
* Validation for Step 3: Contact Information
*
* Validates:
* - Phone numbers (main and additional)
* - Email addresses (main and additional)
* - Registration address (always required)
* - Residence address (conditionally required)
*/
export const contactValidation: ValidationSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// Phone Numbers
// ==========================================
// Main phone (required)
required(path.phoneMain, { message: 'Main phone number is required' });
phone(path.phoneMain, { message: 'Invalid phone format' });
// Additional phone (optional, but must be valid if provided)
phone(path.phoneAdditional, { message: 'Invalid phone format' });
// ==========================================
// Email Addresses
// ==========================================
// Main email (required)
required(path.email, { message: 'Email is required' });
email(path.email, { message: 'Invalid email format' });
// Additional email (optional, but must be valid if provided)
email(path.emailAdditional, { message: 'Invalid email format' });
};
Format validators like email() and phone() automatically skip empty values. That's why we use required() separately for mandatory fields.
Registration Address Validationβ
Add validation for registration address (always required):
export const contactValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
// ... previous validation ...
// ==========================================
// Registration Address (Always Required)
// ==========================================
required(path.registrationAddress.city, { message: 'City is required' });
required(path.registrationAddress.street, { message: 'Street is required' });
required(path.registrationAddress.house, { message: 'House number is required' });
// Apartment is optional, no validation needed
// Postal code (optional, but must be 6 digits if provided)
pattern(path.registrationAddress.postalCode, /^\d{6}$/, {
message: 'Postal code must be 6 digits',
});
};
Residence Address Conditional Validationβ
Add conditional validation for residence address (required only when different from registration):
export const contactValidation: ValidationSchemaFn<CreditApplicationForm> = (path) => {
// ... previous validation ...
// ==========================================
// Residence Address (Conditionally Required)
// ==========================================
applyWhen(
path.sameAsRegistration,
(same) => !same,
(p) => {
required(p.residenceAddress.city, { message: 'City is required' });
required(p.residenceAddress.street, { message: 'Street is required' });
required(p.residenceAddress.house, { message: 'House number is required' });
}
);
pattern(path.residenceAddress.postalCode, /^\d{6}$/, {
message: 'Postal code must be 6 digits',
});
};
Complete Codeβ
Here's the complete validator for Step 3:
import { required, email, phone, pattern, applyWhen } from '@reformer/core/validators';
import type { ValidationSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';
/**
* Validation for Step 3: Contact Information
*
* Validates:
* - Phone numbers (main and additional)
* - Email addresses (main and additional)
* - Registration address (always required)
* - Residence address (conditionally required)
*/
export const contactValidation: ValidationSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// Phone Numbers
// ==========================================
required(path.phoneMain, { message: 'Main phone number is required' });
phone(path.phoneMain, { message: 'Invalid phone format' });
phone(path.phoneAdditional, { message: 'Invalid phone format' });
// ==========================================
// Email Addresses
// ==========================================
required(path.email, { message: 'Email is required' });
email(path.email, { message: 'Invalid email format' });
email(path.emailAdditional, { message: 'Invalid email format' });
// ==========================================
// Registration Address (Always Required)
// ==========================================
required(path.registrationAddress.city, { message: 'City is required' });
required(path.registrationAddress.street, { message: 'Street is required' });
required(path.registrationAddress.house, { message: 'House number is required' });
pattern(path.registrationAddress.postalCode, /^\d{6}$/, {
message: 'Postal code must be 6 digits',
});
// ==========================================
// Residence Address (Conditionally Required)
// ==========================================
applyWhen(
path.sameAsRegistration,
(same) => !same,
(p) => {
required(p.residenceAddress.city, { message: 'City is required' });
required(p.residenceAddress.street, { message: 'Street is required' });
required(p.residenceAddress.house, { message: 'House number is required' });
}
);
pattern(path.residenceAddress.postalCode, /^\d{6}$/, {
message: 'Postal code must be 6 digits',
});
};
How It Worksβ
Email Validatorβ
email(path.email, { message: 'Invalid email format' });
- Built-in email format validation
- Checks for basic email structure:
user@domain.com - Skips empty values (doesn't trigger on empty fields)
- Use with
required()for mandatory fields
Phone Validatorβ
phone(path.phoneMain, { message: 'Invalid phone format' });
- Built-in phone format validation
- Supports various formats:
+7 (999) 123-45-67+79991234567899912345679991234567
- Skips empty values
- Use with
required()for mandatory fields
Conditional Requiredβ
applyWhen(
path.sameAsRegistration, // β Watch this field
(same) => !same, // β Condition: apply when false
(p) => {
required(p.residenceAddress.city, { message: 'City is required' });
required(p.residenceAddress.street, { message: 'Street is required' });
required(p.residenceAddress.house, { message: 'House number is required' });
}
);
How it works:
- Watches
sameAsRegistrationfield - When
sameAsRegistrationchanges, re-evaluates condition - If condition returns
true, applies validators in callback - If condition returns
false, validators are not applied
Integration with Behaviorsβ
Remember from Behaviors section:
// Behavior: Hide residence address when same as registration
disableWhen(path.residenceAddress, path.sameAsRegistration, (same) => same === true);
// Behavior: Disable residence address when same as registration
disableWhen(path.residenceAddress, path.sameAsRegistration, (same) => same === true);
// Validation: Require residence address when different
applyWhen(
path.sameAsRegistration,
(same) => !same,
(p) => {
required(p.residenceAddress.city, { message: 'City is required' });
}
);
Perfect synchronization:
- When
sameAsRegistration = trueβ Field hidden and not required - When
sameAsRegistration = falseβ Field visible and required
Testing the Validationβ
Test these scenarios:
Phone Validationβ
- Leave main phone empty β Error shown
- Enter invalid main phone format β Error shown
- Enter valid main phone β No error
- Enter invalid additional phone β Error shown (even though optional)
- Leave additional phone empty β No error
Email Validationβ
- Leave main email empty β Error shown
- Enter invalid email format (no @) β Error shown
- Enter invalid email format (no domain) β Error shown
- Enter valid email β No error
- Enter invalid additional email β Error shown (even though optional)
- Leave additional email empty β No error
Registration Addressβ
- Leave city empty β Error shown
- Leave street empty β Error shown
- Leave house empty β Error shown
- Leave apartment empty β No error (optional)
- Enter invalid postal code (5 digits) β Error shown
- Enter invalid postal code (letters) β Error shown
- Enter valid postal code (6 digits) β No error
- Leave postal code empty β No error (optional)
Residence Addressβ
- Check "same as registration" β Residence fields not required
- Uncheck "same as registration" β Residence fields become required
- Leave residence city empty (when different) β Error shown
- Leave residence street empty (when different) β Error shown
- Leave residence house empty (when different) β Error shown
Supported Phone Formatsβ
The phone() validator accepts various formats:
// All valid formats:
'+7 (999) 123-45-67';
'+79991234567';
'89991234567';
'9991234567';
'+1 (234) 567-8901'; // International
Supported Email Formatsβ
The email() validator follows standard email format:
// Valid emails:
'user@example.com';
'user.name@example.com';
'user+tag@example.co.uk';
'user_name123@sub.example.org';
// Invalid emails:
'user@'; // No domain
'@example.com'; // No user
'user @example.com'; // Space in username
'user@example'; // No TLD
Key Takeawaysβ
- Format Validators - Use built-in
email()andphone()for formats - Separate Required - Format validators skip empty values
- Conditional Required - Use
applyWhen()for dynamic requirements - Works with Behaviors - Hidden/disabled fields skip validation
- Optional Validation - Can validate format even when not required
Common Patternsβ
Required Emailβ
required(path.email, { message: 'Email is required' });
email(path.email, { message: 'Invalid email format' });
Optional Email (validates format if provided)β
email(path.emailAdditional, { message: 'Invalid email format' });
// No required() - field is optional
Russian Postal Codeβ
pattern(path.postalCode, /^\d{6}$/, {
message: 'Postal code must be 6 digits',
});
What's Next?β
In the next section, we'll add validation for Step 4: Employment, including:
- Required employment status
- Conditional validation for employed vs self-employed
- Income validation with minimum thresholds
- Work experience validation
- Business-specific field validation
We'll continue using the conditional validation patterns learned here!