Skip to main content

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 submitting
  • change — validation runs on every change
  • blur — 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>
tip

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:

  1. The form is not ready (missing required values and/or has validation errors)
  2. A request is in flight (isSubmitting)

Formora gives you values, errors, and isValid. Your app owns submit/loading state.

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