Step 3: Contact Information Behaviors
Managing address synchronization and conditional visibility.
Overviewβ
For Step 3 (Contact Information), we'll implement:
- Conditional Visibility - Hide residence address when same as registration
- Conditional Access - Disable residence address when same as registration
- Data Synchronization - Copy registration address to residence address
This demonstrates a common pattern: giving users the option to use the same value for multiple fields.
Creating the Behavior Fileβ
touch reformer-tutorial/src/forms/credit-application/schemas/behaviors/contact-info.ts
Implementing the Behaviorsβ
import { disableWhen, disableWhen, copyTo } from '@reformer/core/behaviors';
import type { BehaviorSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';
export const contactBehaviorSchema: BehaviorSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
// ==========================================
// 1. Hide Residence Address When Same as Registration
// ==========================================
disableWhen(path.residenceAddress, path.sameAsRegistration, (value) => value === true);
// ==========================================
// 2. Disable Residence Address When Same as Registration
// ==========================================
disableWhen(path.residenceAddress, path.sameAsRegistration, (value) => value === true);
// ==========================================
// 3. Copy Registration Address to Residence Address
// ==========================================
copyTo(
path.registrationAddress, // Source
path.residenceAddress, // Target
path.sameAsRegistration, // Condition field
(shouldCopy) => shouldCopy === true // When to copy
);
};
Understanding Each Behaviorβ
1. disableWhen:
- Hides the
residenceAddressfields from the UI - Fields are not validated when hidden
- Fields are not included in form submission
2. disableWhen:
- Makes
residenceAddressfields read-only - Prevents user from editing
- Works together with
disableWhen(though hidden fields are already inaccessible)
3. copyTo:
- Watches
registrationAddressfor changes - When
sameAsRegistrationistrue, copies the value toresidenceAddress - Runs whenever
registrationAddresschanges while condition is met
You might wonder why we use both disableWhen and disableWhen for the same condition:
disableWhen- Removes fields from UI completely (cleaner UX)disableWhen- Prevents editing if fields are shown
While redundant here, in some cases you might want disabled-but-visible fields. Using both is defensive programming.
How copyTo Worksβ
The copyTo behavior creates a smart synchronization:
copyTo(
sourceField, // What to copy FROM
targetField, // What to copy TO
conditionField, // Field that determines if copying should happen
conditionFn // Function that evaluates the condition
);
Execution flow:
- User fills in registration address (city, street, house, etc.)
- User checks "Same as registration" checkbox
copyToimmediately copiesregistrationAddressβresidenceAddress- If user modifies
registrationAddress,residenceAddressupdates automatically - If user unchecks "Same as registration", copying stops (but value remains)
copyTo- One-way copying (source β target)syncWith- Two-way synchronization (source β target)
For addresses, copyTo is correct because we don't want changes to residenceAddress to affect registrationAddress.
Complete Codeβ
import { disableWhen, disableWhen, copyTo } from '@reformer/core/behaviors';
import type { BehaviorSchemaFn, FieldPath } from '@reformer/core';
import type { CreditApplicationForm } from '@/types';
export const contactBehaviorSchema: BehaviorSchemaFn<CreditApplicationForm> = (
path: FieldPath<CreditApplicationForm>
) => {
disableWhen(path.residenceAddress, path.sameAsRegistration, (value) => value === true);
disableWhen(path.residenceAddress, path.sameAsRegistration, (value) => value === true);
copyTo(
path.registrationAddress,
path.residenceAddress,
path.sameAsRegistration,
(shouldCopy) => shouldCopy === true
);
};
Testingβ
Test Scenariosβ
-
Address Copying:
- Fill in registration address (all fields)
- Check "Same as registration" checkbox
- Verify residence address is filled automatically
- Modify registration address
- Verify residence address updates automatically
-
Conditional Visibility:
- With "Same as registration" checked β Residence address should be hidden
- Uncheck the checkbox β Residence address should appear
- Check again β Address should hide again (with copied values)
-
Manual Override:
- Check "Same as registration" (address copied)
- Uncheck it
- Modify residence address manually
- Check "Same as registration" again
- Verify manual changes are overwritten by registration address
UI Integrationβ
In your contact info step component:
function ContactInfoStep({ control }: Props) {
return (
<div className="space-y-6">
{/* Registration Address */}
<div>
<h3 className="font-semibold mb-4">Registration Address</h3>
<AddressForm control={control.registrationAddress} testIdPrefix="registration" />
</div>
{/* Same as Registration Checkbox */}
<FormField control={control.sameAsRegistration} />
{/* Residence Address - will hide/disable automatically */}
<div>
<h3 className="font-semibold mb-4">Residence Address</h3>
<AddressForm control={control.residenceAddress} testIdPrefix="residence" />
</div>
</div>
);
}
The behaviors handle the rest automatically!
Resultβ
Step 3 now has:
- β Smart address copying (registration β residence)
- β Conditional visibility (hide when same)
- β Conditional access control (disable when same)
- β Clean UX (no manual duplication needed)
Key Takeawaysβ
disableWhenfor conditional visibilitydisableWhenfor conditional access controlcopyTofor one-way data synchronization- Combine multiple behaviors for robust UX
- Behaviors eliminate manual checkbox handling logic
Next Stepβ
Let's add behaviors for Step 4: Employment, where we'll handle different employment types with conditional fields and income calculations.