Skip to main content

Schemas Overview

ReFormer uses a three-schema architecture to separate concerns and enable maximum code reuse.

The Three Schemas​

SchemaPurposeProperty
Form SchemaData structure and field configurationform
Validation SchemaValidation rulesvalidation
Behavior SchemaReactive logic and side effectsbehavior
import { GroupNode } from '@reformer/core';
import { required, email } from '@reformer/core/validators';
import { computeFrom } from '@reformer/core/behaviors';

const form = new GroupNode({
// 1. Form Schema - structure
form: {
firstName: { value: '' },
lastName: { value: '' },
fullName: { value: '' },
email: { value: '' },
},

// 2. Validation Schema - rules
validation: (path) => {
required(path.firstName);
required(path.lastName);
required(path.email);
email(path.email);
},

// 3. Behavior Schema - logic
behavior: (path) => {
computeFrom([path.firstName, path.lastName], path.fullName, ({ firstName, lastName }) =>
`${firstName} ${lastName}`.trim()
);
},
});

Why Three Schemas?​

Separation of Concerns​

Each schema has a single responsibility:

  • Form Schema: "What data do we collect?"
  • Validation Schema: "Is the data correct?"
  • Behavior Schema: "How should data react to changes?"

Reusability & Decomposition​

Each schema can be decomposed into reusable parts and combined using the apply function:

import { apply, required } from '@reformer/core/validators';
import { apply as applyBehavior, watchField } from '@reformer/core/behaviors';

// 1. Reusable form schema (always use factory functions!)
const addressSchema = (): FormSchema<Address> => ({
street: { value: '' },
city: { value: '' },
zipCode: { value: '' },
});

// 2. Reusable validation schema
const addressValidation: ValidationSchemaFn<Address> = (path) => {
required(path.street);
required(path.city);
required(path.zipCode);
};

// 3. Reusable behavior schema
const addressBehavior: BehaviorSchemaFn<Address> = (path) => {
watchField(path.zipCode, (value, ctx) => {
// Format ZIP code
});
};

// Compose into forms using apply()
const orderForm = new GroupNode<OrderForm>({
form: {
billingAddress: addressSchema(),
shippingAddress: addressSchema(),
},
validation: (path) => {
// Apply same validation to multiple fields
apply([path.billingAddress, path.shippingAddress], addressValidation);
},
behavior: (path) => {
// Apply same behavior to multiple fields
applyBehavior([path.billingAddress, path.shippingAddress], addressBehavior);
},
});

The apply function supports flexible composition:

// Single field + single schema
apply(path.address, addressValidation);

// Multiple fields + single schema
apply([path.billingAddress, path.shippingAddress], addressValidation);

// Single field + multiple schemas
apply(path.email, [requiredValidation, emailValidation]);

// Multiple fields + multiple schemas
apply([path.email, path.phone], [requiredValidation, formatValidation]);
Factory Functions

Always use functions that return schemas (addressSchema()) instead of direct objects. This ensures each form gets its own instance and avoids shared state bugs.

Benefits of decomposition:

  • DRY β€” Write once, use everywhere
  • Consistency β€” Same rules across all forms
  • Maintainability β€” Update in one place
  • Testing β€” Test each part in isolation

See Composition for complete patterns and best practices.

Testability​

Test each schema in isolation:

// Test validation separately
describe('validatePerson', () => {
it('requires firstName', () => {
const form = new GroupNode({
form: personSchema(),
validation: validatePerson,
});
expect(form.controls.firstName.errors).toEqual({ required: true });
});
});

Type Safety​

All three schemas use FieldPath<T> for compile-time type checking:

validation: (path) => {
required(path.firstName); // βœ… TypeScript knows this exists
required(path.middleName); // ❌ Error: 'middleName' doesn't exist
};

Schema Structure​

GroupNode Config
β”œβ”€β”€ form: FormSchema<T> β†’ Data structure
β”œβ”€β”€ validation: ValidationSchemaFn<T> β†’ Validation rules
└── behavior: BehaviorSchemaFn<T> β†’ Reactive logic

Next Steps​