Usage Examples
Scenarios of working with the AI when @reformer/mcp is connected.
Scenario 1: Creating a formβ
Request to the AI:
Create a registration form with fields:
- Email
- Password (minimum 8 characters)
- Password confirmation
- Terms agreement checkbox
What you get:
The AI uses the ReFormer documentation and creates:
- A TypeScript interface:
interface RegistrationForm {
email: string;
password: string;
confirmPassword: string;
agreeToTerms: boolean;
}
-
The form schema β a tree of
{ model: model.$.email, component, componentProps }nodes -
Validation rules β a separate layer (
@reformer/core/validation), not part of the layout schema:
import { validate, defineValidation } from '@reformer/core/validation';
import { required, email, minLength } from '@reformer/core/validators';
import type { ValidationIssue } from '@reformer/core';
// Password matching reads the second field β it is a rule over several fields (a model snapshot).
const passwordsMatch = (f: RegistrationForm): ValidationIssue | null =>
f.confirmPassword && f.password && f.confirmPassword !== f.password
? { code: 'passwords-mismatch' }
: null;
export const registrationRules = defineValidation<RegistrationForm>(({ model, cross }) => {
validate(model.$.email, [required(), email()]);
validate(model.$.password, [required(), minLength(8)]);
validate(model.$.confirmPassword, [required()]);
cross(model.$.confirmPassword, passwordsMatch);
// An inline rule is a plain (value) => ValidationIssue | null function.
validate(model.$.agreeToTerms, [(value) => (value ? null : { code: 'terms-required' })]);
});
Rules carry only issue codes; the texts for the codes live in the form's dictionary:
import { defineIssueMessages } from '@reformer/core/i18n';
export const registrationMessages = defineIssueMessages({
'passwords-mismatch': 'Passwords do not match',
'terms-required': 'You must accept the terms',
});
- A React component: one
createForm({ initial, schema, validation: registrationRules })bundle inuseFormBundle, fields β<FormField control={form.email} />under<ValidationMessagesProvider messages={registrationMessages}>, submission βbundle.submission.submit(handler): if there are issues, the handler is not called and the fields are highlighted.
Scenario 2: Adding validationβ
Request to the AI:
Add validation to the order form:
- phone field: required, format +7 (XXX) XXX-XX-XX
- deliveryDate field: not earlier than tomorrow
- If deliveryType === "express", the deliveryTime field is required
What you get:
import { validate, validateWhen, defineValidation } from '@reformer/core/validation';
import { required, pattern } from '@reformer/core/validators';
import type { ValidationIssue } from '@reformer/core';
// Inline rule: delivery date not earlier than tomorrow. Returns ValidationIssue | null.
const notBeforeTomorrow = (value: string): ValidationIssue | null => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0, 0, 0, 0);
return new Date(value) >= tomorrow ? null : { code: 'deliveryTooSoon' };
};
const orderRules = defineValidation<OrderForm>(({ model }) => {
validate(model.$.phone, [
required(),
pattern(/^\+7 \(\d{3}\) \d{3}-\d{2}-\d{2}$/, { code: 'phone.pattern' }),
]);
validate(model.$.deliveryDate, [required(), notBeforeTomorrow]);
// Conditional branch: the deliveryTime rule is active only for express delivery,
// otherwise it is silenced (the error is cleared automatically).
validateWhen(
() => model.deliveryType === 'express',
() => validate(model.$.deliveryTime, [required({ code: 'deliveryTime.required' })])
);
});
Scenario 3: Computed fieldsβ
Request to the AI:
In the order form add:
- Automatic subtotal calculation (quantity * price) for each item
- A grand total across all items
- A 10% discount if the grand total is over 10000
What you get:
import { applyEach, compute, defineFormBehavior } from '@reformer/core/behaviors';
type OrderItem = { quantity: number | null; price: number | null; subtotal: number };
type OrderForm = { items: OrderItem[]; total: number; discount: number; finalTotal: number };
// Row behavior: the subtotal for an item
const itemBehavior = defineFormBehavior<OrderItem>(({ model: row }) => {
compute(row.$.subtotal, () => (row.quantity ?? 0) * (row.price ?? 0));
});
export const orderBehavior = defineFormBehavior<OrderForm>(({ model }) => {
applyEach(model.$.items, itemBehavior); // to every row, including ones added later
compute(model.$.total, () => model.items.map((item) => item.subtotal).reduce((a, b) => a + b, 0));
compute(model.$.discount, () => (model.total > 10000 ? model.total * 0.1 : 0));
compute(model.$.finalTotal, () => model.total - model.discount);
});
Scenario 4: Debugging a problemβ
Request to the AI:
My form doesn't show validation errors. What's wrong?
const MyForm = () => {
const { form } = createForm<UserForm>({ initial: { email: '', password: '' }, schema: buildSchema });
const email = useFormControl(form.email);
// userRules are declared, but never passed to the bundle and never checked anywhere
return (
<input
value={email.value}
onChange={(e) => form.email.setValue(e.target.value)}
/>
);
};
What you get:
The AI will find the problems:
- The bundle is recreated on every render β there is no
useFormBundle(the nodes and their state are lost) - The rules are not passed to the bundle (
validation), and there is no submission viabundle.submission.submitβ there is nothing to check and nothing to check it - There is no
markAsTouchedcall on blur β the field stays "untouched", so the error is hidden (shouldShowIssues= false) - Issues are not displayed anywhere:
issuesis an array of{ code, params? }, and the text for a code comes fromuseIssueMessage()(orFormFieldwithValidationMessagesProvider)
And it will suggest a fix:
import { createForm } from '@reformer/core';
import { useFormBundle, useFormControl, useIssueMessage } from '@reformer/core/react';
const MyForm = () => {
// β
the bundle is created once, and the rules are passed to it
const bundle = useFormBundle(() =>
createForm<UserForm>({
initial: { email: '', password: '' },
schema: buildSchema,
validation: userRules,
})
);
const { form } = bundle;
const email = useFormControl(form.email);
const message = useIssueMessage();
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
// β
a single run of the rules; if there are issues, the handler is not called
// and the whole form is marked touched
await bundle.submission.submit((values) => api.save(values));
};
return (
<form onSubmit={handleSubmit}>
<input
value={email.value}
onChange={(event) => form.email.setValue(event.target.value)}
onBlur={() => form.email.markAsTouched()}
aria-invalid={email.shouldShowError}
/>
{email.shouldShowIssues && email.issues[0] && (
<span className="error">{message(email.issues[0])}</span>
)}
<button type="submit">Submit</button>
</form>
);
};
Scenario 5: API questionsβ
Requests to the AI:
Which validators does ReFormer provide out of the box?
How does the watchField behavior work?
How do I do async validation?
The AI will answer based on up-to-date ReFormer documentation.
Tips for working with the AIβ
Be specificβ
β Bad:
Make a form
β Good:
Create an order form with fields: full name, phone, email, delivery address.
Phone in +7 format. Email is required. Address at least 10 characters.
Provide contextβ
When debugging, always include the code:
Error "Cannot read property 'value' of undefined" on line 15.
[your code]
Use iterationsβ
Start with a basic form, then add functionality:
- "Create a basic registration form"
- "Add email validation"
- "Add a password match check"
- "Add a conditional field for the company"