Behavior Schema
Behavior Schema defines reactive logic and side effects for your form.
BehaviorSchemaFn Typeβ
type BehaviorSchemaFn<T> = (path: FieldPath<T>) => void;
The behavior function receives a type-safe path object for declaring reactive behaviors:
import { GroupNode } from '@reformer/core';
import { computeFrom, enableWhen } from '@reformer/core/behaviors';
const form = new GroupNode({
form: {
price: { value: 100 },
quantity: { value: 1 },
total: { value: 0 },
discount: { value: 0 },
},
behavior: (path) => {
// Auto-compute total
computeFrom([path.price, path.quantity], path.total, ({ price, quantity }) => price * quantity);
// Enable discount only for large orders
enableWhen(path.discount, (form) => form.total > 500);
},
});
Available Behaviorsβ
| Behavior | Purpose | Description |
|---|---|---|
computeFrom | Computed | Calculate field from other fields |
transformValue | Computed | Transform value on change |
enableWhen | Conditional | Enable/disable based on condition |
resetWhen | Conditional | Reset field when condition met |
copyFrom | Sync | Copy value from another field |
syncFields | Sync | Two-way field synchronization |
watchField | Watch | React to field changes |
revalidateWhen | Watch | Trigger revalidation |
Computed Behaviorsβ
computeFromβ
Calculate a field's value from other fields:
import { computeFrom } from '@reformer/core/behaviors';
behavior: (path) => {
// Single source
computeFrom([path.firstName], path.initials, ({ firstName }) =>
firstName.charAt(0).toUpperCase()
);
// Multiple sources
computeFrom([path.firstName, path.lastName], path.fullName, ({ firstName, lastName }) =>
`${firstName} ${lastName}`.trim()
);
};
transformValueβ
Transform value on change:
import { transformValue } from '@reformer/core/behaviors';
behavior: (path) => {
// Uppercase username
transformValue(path.username, (value) => value.toLowerCase());
// Format phone number
transformValue(path.phone, (value) => {
const digits = value.replace(/\D/g, '');
if (digits.length === 10) {
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
}
return value;
});
};
Conditional Behaviorsβ
enableWhenβ
Enable or disable field based on condition:
import { enableWhen } from '@reformer/core/behaviors';
behavior: (path) => {
// Enable shipping address when not same as billing
enableWhen(path.shippingAddress, (form) => !form.sameAsBilling);
// Enable discount for premium users
enableWhen(path.discount, (form) => form.userType === 'premium');
};
resetWhenβ
Reset field when condition is met:
import { resetWhen } from '@reformer/core/behaviors';
behavior: (path) => {
// Reset shipping when "same as billing" checked
resetWhen(path.shippingAddress, (form) => form.sameAsBilling === true);
};
Sync Behaviorsβ
copyFromβ
Copy value from another field:
import { copyFrom } from '@reformer/core/behaviors';
behavior: (path) => {
// Copy billing to shipping when checkbox checked
copyFrom(path.billingAddress, path.shippingAddress, { when: (form) => form.sameAsBilling });
};
syncFieldsβ
Two-way synchronization:
import { syncFields } from '@reformer/core/behaviors';
behavior: (path) => {
// Sync email fields (changes in either update both)
syncFields(path.email, path.confirmEmail);
};
Watch Behaviorsβ
watchFieldβ
React to field changes:
import { watchField } from '@reformer/core/behaviors';
behavior: (path) => {
watchField(path.country, (value, ctx) => {
// Load states/provinces for selected country
loadStates(value).then((states) => {
ctx.form.stateOptions.setValue(states);
});
});
};
revalidateWhenβ
Trigger revalidation when another field changes:
import { revalidateWhen } from '@reformer/core/behaviors';
behavior: (path) => {
// Revalidate password confirmation when password changes
revalidateWhen(path.password, path.confirmPassword);
};
Extracting Behavior Setsβ
Create reusable behavior functions:
import { FieldPath, Behavior } from '@reformer/core';
import { computeFrom, transformValue } from '@reformer/core/behaviors';
// Reusable behaviors for address
export function addressBehaviors<T extends { address: Address }>(path: FieldPath<T>) {
// Auto-format ZIP code
transformValue(path.address.zipCode, (value) => {
const digits = value.replace(/\D/g, '');
if (digits.length === 9) {
return `${digits.slice(0, 5)}-${digits.slice(5)}`;
}
return value;
});
}
// Usage
const form = new GroupNode({
form: {
billing: addressSchema(),
shipping: addressSchema(),
},
behavior: (path) => {
addressBehaviors(path.billing);
addressBehaviors(path.shipping);
},
});
Behavior vs Validationβ
| Aspect | Validation | Behavior |
|---|---|---|
| Purpose | Check correctness | React to changes |
| Output | Errors | Side effects |
| When runs | After value change | After value change |
| Examples | Required, email format | Computed total, show/hide |
Next Stepsβ
- Behaviors Overview β Detailed behaviors guide
- Computed Fields β
computeFrom,transformValue - Conditional Logic β
enableWhen,resetWhen - Composition β Reuse behavior sets