register(name, rules)
register connects an input to Formora.
As a developer, you use it to:
- bind an input to
values[name] - update the value on change
- mark the field as touched on blur
- run validation according to
validateOn
Signature
register(
name: string,
rules?: {
required?: string;
minLength?: { value: number; message: string };
maxLength?: { value: number; message: string };
pattern?: { value: RegExp; message: string };
min?: { value: number; message: string };
max?: { value: number; message: string };
validate?: (value: unknown, values: any) => string | undefined;
}
): {
name: string;
value: any;
onChange: (e: any) => void;
onBlur: () => void;
}
Validation is sync. Return undefined when valid, or a string error message when invalid.
name is a path
Formora treats field names as paths.
Examples:
emailprofile.address.streetitems.0.name
This is how nested objects and arrays work without special APIs.
Minimal usage
<input {...form.register("email")} />
You’ll typically pair it with validation rules:
<input
{...form.register("email", {
required: "Email is required",
pattern: { value: /^\S+@\S+$/i, message: "Invalid email" },
})}
/>;
{
form.errors?.email && <p>{String(form.errors.email)}</p>;
}
Built-in rules (recommended)
Prefer these for common cases:
requiredminLength,maxLengthpatternmin,max
Example:
<input
type="password"
{...form.register("password", {
required: "Password is required",
minLength: { value: 6, message: "Minimum 6 characters" },
})}
/>
Custom validation (advanced)
Use validate when the built-in rules aren’t enough.
<input
{...form.register("password", {
validate: (value, values) => {
const v = String(value ?? "");
if (!v) return "Password is required";
if (v.length < 6) return "Minimum 6 characters";
if (v.includes("password")) return "Too easy";
return undefined;
},
})}
/>
Access to all values
validate receives the full values object as the second argument (useful for cross-field rules).
validate: (value, values) => {
if (String(value ?? "") !== String(values.confirmPassword ?? "")) {
return "Passwords do not match";
}
return undefined;
};
Handling common input types
Formora supports common DOM inputs directly through register.
Checkbox → boolean
<input type="checkbox" {...form.register("acceptTerms")} />
Radio
⚠️ Important: put value="..." after register() so the radio value is not overridden.
<input type="radio" {...form.register("plan")} value="basic" />
<input type="radio" {...form.register("plan")} value="pro" />
Select (single)
<select {...form.register("country")}>...</select>
Select (multiple) → string[]
<select multiple {...form.register("tags")}>
...
</select>
Number inputs
Browsers often provide number values as strings. If you need numeric logic, coerce inside validate or on submit.
<input
type="number"
{...form.register("age", {
validate: (v) => {
if (!v) return "Required";
const n = Number(v);
if (Number.isNaN(n)) return "Invalid number";
if (n < 18) return "Must be 18+";
return undefined;
},
})}
/>
Error visibility (developer choice)
Formora stores errors in form.errors. You decide when to show them.
Common patterns:
- show immediately (live forms)
- show after blur (
touched) - show after submit (submit mode)
Example (blur UX):
<input {...form.register("email", { required: "Email is required" })} />;
{
form.touched?.email && form.errors?.email && (
<p>{String(form.errors.email)}</p>
);
}
Summary
registerconnects an input to Formoranameis a path (nested fields supported)- prefer built-in rules (
required,minLength,pattern, etc.) - use
validatefor advanced/custom logic - checkbox/radio/select/multi-select are supported (see examples above)