Watch Behaviors
React to field changes with custom logic.
watchFieldβ
Execute callback when field value changes.
import { watchField } from '@reformer/core/behaviors';
behaviors: (path, ctx) => [
watchField(path.country, (newValue, oldValue) => {
console.log(`Country changed from ${oldValue} to ${newValue}`);
// Load cities for new country
loadCities(newValue);
}),
];
Example: Dynamic Optionsβ
const form = new GroupNode({
form: {
category: { value: '' },
subcategory: { value: '' },
},
behaviors: (path, ctx) => [
watchField(path.category, async (category) => {
// Reset subcategory
form.controls.subcategory.setValue('');
// Fetch subcategories
const options = await fetchSubcategories(category);
setSubcategoryOptions(options);
}),
],
});
Example: Analyticsβ
behaviors: (path, ctx) => [
watchField(path.step, (step) => {
analytics.track('form_step_changed', { step });
}),
];
revalidateWhenβ
Trigger field revalidation when another field changes.
import { revalidateWhen } from '@reformer/core/behaviors';
behaviors: (path, ctx) => [
// Revalidate confirmPassword when password changes
revalidateWhen(path.confirmPassword, [path.password]),
];
Example: Date Rangeβ
import { validate } from '@reformer/core/validators';
const form = new GroupNode({
form: {
startDate: { value: '' },
endDate: { value: '' },
},
validation: (path) => {
validate(path.endDate, (value, ctx) => {
const start = ctx.root.controls.startDate.value;
if (start && value && value < start) {
return { endBeforeStart: true };
}
return null;
});
},
behaviors: (path, ctx) => [
// Revalidate endDate when startDate changes
revalidateWhen(path.endDate, [path.startDate]),
],
});
Example: Cross-Field Validationβ
behaviors: (path, ctx) => [
// Password strength depends on username (can't contain it)
revalidateWhen(path.password, [path.username]),
// Confirm password must match password
revalidateWhen(path.confirmPassword, [path.password]),
];
Multiple Watchersβ
Watch multiple fields:
behaviors: (path, ctx) => [
watchField([path.firstName, path.lastName], () => {
// Called when either changes
updateDisplayName();
}),
];
Debounced Watchβ
Prevent too frequent updates:
behaviors: (path, ctx) => [
watchField(
path.searchQuery,
async (query) => {
const results = await search(query);
setSearchResults(results);
},
{ debounce: 300 }
),
];
Watch with Cleanupβ
Return cleanup function:
behaviors: (path, ctx) => [
watchField(path.livePreview, (enabled) => {
if (enabled) {
const interval = setInterval(refreshPreview, 1000);
return () => clearInterval(interval); // Cleanup
}
}),
];
Combining Watch with Other Behaviorsβ
behaviors: (path, ctx) => [
// Show premium fields
enableWhen(path.premiumOptions, () => form.controls.plan.value === 'premium'),
// Track plan changes
watchField(path.plan, (plan) => {
analytics.track('plan_selected', { plan });
}),
// Revalidate dependent fields
revalidateWhen(path.features, [path.plan]),
];
Next Stepsβ
- Validation β Combine with validation
- React Integration β Use in React components