Recipes & Common Patterns
Practical examples of real-world form scenarios with mobx-react-form.
Dependent Fields
Validate one field based on the value of another:
import { Form } from 'mobx-react-form';
const fields = {
country: {
label: 'Country',
value: 'US',
},
state: {
label: 'State',
rules: 'required|string',
},
};
const hooks = {
onChange(form) {
// Clear state when country changes
if (form.$('country').changed) {
form.$('state').reset();
}
},
};
const form = new Form({ fields }, { hooks });
Conditional Validation
Apply different validation rules based on another field's value:
// Custom VJF validator
function requiredIf(conditionField, conditionValue) {
return ({ field, form }) => {
const conditionMet = form.$(conditionField).value === conditionValue;
if (!conditionMet) return [true]; // skip validation
const isValid = field.value && field.value.length > 0;
return [isValid, `The ${field.label} is required.`];
};
}
const fields = {
subscribe: {
label: 'Subscribe to newsletter',
type: 'checkbox',
value: false,
},
email: {
label: 'Email',
validators: [requiredIf('subscribe', true)],
},
};
Multi-Step / Wizard Forms
Use the Forms Composer to manage multi-step flows:
import { composer } from 'mobx-react-form/lib/composer';
import StepOneForm from './StepOneForm';
import StepTwoForm from './StepTwoForm';
import StepThreeForm from './StepThreeForm';
const forms = composer({
step1: new StepOneForm(),
step2: new StepTwoForm(),
step3: new StepThreeForm(),
});
// Navigate steps
function nextStep(currentStep) {
const current = forms.select(currentStep);
current.submit({
onSuccess: () => {
if (currentStep === 'step3') {
forms.submit().then(({ forms }) => {
console.log('All steps valid!', forms);
});
}
},
onError: () => alert('Fix errors before proceeding'),
});
}
Dynamic Array Fields
Add and remove fields from an array dynamically:
const fields = [
'hobbies',
'hobbies[]',
];
const values = {
hobbies: ['Soccer', 'Baseball'],
};
const form = new Form({ fields, values });
// Add a new hobby
form.$('hobbies').add({ value: 'Tennis' });
// Remove the second hobby (index 1)
form.$('hobbies').del(1);
// Remove by path
form.del('hobbies[0]');
File Upload with Preview
const fields = {
avatar: {
label: 'Profile Picture',
type: 'file',
hooks: {
onDrop(field) {
const file = field.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
field.set('extra', {
previewUrl: e.target.result,
fileName: file.name,
fileSize: file.size,
});
};
reader.readAsDataURL(file);
}
},
},
},
};
Debounced Search / Autocomplete
Use the built-in debounced validation for search-as-you-type:
const fields = {
search: {
label: 'Search',
rules: 'min:3',
},
};
const options = {
validationDebounceWait: 400, // wait 400ms after typing
validateOnChange: true,
};
const hooks = {
onSuccess(form) {
if (form.$('search').changed) {
performSearch(form.$('search').value);
}
},
};
const form = new Form({ fields }, { options, hooks });
Reset to Specific Values
Use set() followed by resetValidation() to restore specific state:
function resetToPreset(form, preset) {
form.set('value', preset);
form.resetValidation(true);
}
Check if Any Field is Dirty Across Nested Fields
const isFormDirty = form.changed > 0;
// or for the whole form tree:
const isAnyDirty = form.check('isDirty', true);
Invalidate with Async Error
Mark a field as invalid after an API call:
form.submit({
onSuccess: (form) => {
api.createUser(form.values())
.catch((error) => {
// Server returned a field-level error
form.$('email').invalidate(error.email);
// Or set a generic form error
form.invalidate('Unable to create account. Please try again.');
});
},
});
Computed Values Example
Automatically compute a total from array fields:
const computed = {
'products[].total': ({ field }) => {
const qty = field.container()?.$('qty')?.value || 0;
const amount = field.container()?.$('amount')?.value || 0;
return qty * amount;
},
total: ({ form }) =>
form.$('products')?.reduce(
(acc, f) => acc + (f.$('total')?.value || 0), 0
),
};
Tips
- Use
strictSelect: falseif you access fields via computed props before they are fully initialized. - Use
validateOnChangeAfterInitialBlur: trueto only validate on change after the user has left the field once. - Use
autoTrimValue: trueto automatically trim whitespace from string values.