Skip to main content

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;
}
info

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:

  • email
  • profile.address.street
  • items.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>;
}

Prefer these for common cases:

  • required
  • minLength, maxLength
  • pattern
  • min, 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

  • register connects an input to Formora
  • name is a path (nested fields supported)
  • prefer built-in rules (required, minLength, pattern, etc.)
  • use validate for advanced/custom logic
  • checkbox/radio/select/multi-select are supported (see examples above)