useForm
useForm is the main entry point of Formora.
It creates a form controller that manages form state and validation in a predictable, explicit way.
What useForm does
As a developer, you can think of useForm as responsible for:
- tracking form values
- running synchronous validation
- tracking errors, touched, and dirty state
- telling you whether the form is currently valid (
isValid)
What it does not do:
- ❌ async validation
- ❌ network or submit state
- ❌ UI decisions (you control when to show errors)
Signature
function useForm<TValues>(options: {
initialValues: TValues;
validateOn?: "submit" | "change" | "blur";
}): {
values: TValues;
errors: any;
touched: any;
dirty: any;
isValid: boolean;
register: (name: string, rules?: any) => any;
handleSubmit: (onValid: (values: TValues) => void) => (e?: any) => void;
reset: () => void;
};
Options
initialValues
Type: TValues
Initial values for the form. These are used as the baseline for:
- value initialization
- dirty checking
reset()
Field names are treated as paths, so nested objects and arrays are supported.
validateOn
Type: "submit" | "change" | "blur"
Controls when validation runs:
submit— validation runs only when submittingchange— validation runs on every changeblur— validation runs when a field loses focus
This option applies to the entire form.
Returned state
values
Current form values. Shape always matches initialValues.
errors
Nested error object. Errors are strings at leaf nodes.
Example:
{
email: "Required",
profile: {
address: {
street: "Too short",
},
},
}
touched
Nested boolean map indicating whether a field has been touched (blurred).
dirty
Nested boolean map indicating whether a field differs from its initial value.
isValid
true when the form has no validation errors.
isValid is not the same as “ready to submit”isValid only means there are no errors right now.
Depending on your validateOn mode, validation may not have run yet (for example, before the user types or blurs a field). In that case errors can be empty and isValid may be true on the first render.
If you want to disable a button until required fields are filled, combine isValid with your own “required values present” check.
Example:
const requiredFilled = Boolean(form.values.email && form.values.password);
const canSubmit = requiredFilled && form.isValid;
<button disabled={!canSubmit}>Submit</button>;
Core methods
register(name, rules)
Connects an input to Formora.
- binds the input value
- updates form state on change
- marks the field as touched on blur
- runs validation according to
validateOn
See: register.
handleSubmit(onValid)
Returns a submit handler.
Behavior:
- runs submit validation
- if valid → calls
onValid(values) - if invalid → populates
errors
Example:
<form onSubmit={form.handleSubmit((values) => console.log(values))}>...</form>
handleSubmit works with or without a <form>. You can attach it to onSubmit or call it from any button onClick.
reset()
Resets the form back to initialValues.
This clears:
- values
- errors
- touched
- dirty
Common patterns: disabling a submit button
There are usually two reasons to disable a submit button:
- The form is not ready (missing required values and/or has validation errors)
- A request is in flight (
isSubmitting)
Formora gives you values, errors, and isValid. Your app owns submit/loading state.
Pattern A (recommended): required values + isValid + isSubmitting
const [isSubmitting, setIsSubmitting] = React.useState(false);
const requiredFilled = Boolean(form.values.email && form.values.password);
const canSubmit = requiredFilled && form.isValid && !isSubmitting;
<button disabled={!canSubmit} type="submit">
{isSubmitting ? "Saving…" : "Submit"}
</button>;
Pattern B: disable until the user interacts
Useful when you want to prevent submitting untouched defaults.
const interacted = Boolean(
form.dirty?.email ||
form.dirty?.password ||
form.touched?.email ||
form.touched?.password
);
<button disabled={!interacted || !form.isValid}>Submit</button>;
Using Formora without a <form>
You can still validate and submit without a <form> element by calling handleSubmit from a normal button click.
const submit = form.handleSubmit(async (values) => {
// do your request here
});
<button type="button" onClick={submit} disabled={!canSubmit}>
Submit
</button>;