useFormState
The central hook that wires everything together. It builds the Zod schema from your field configs, initializes React Hook Form, runs plugin lifecycle hooks, and coordinates step navigation with submission.
You probably don’t need this directly. The
<Form />component callsuseFormStateinternally. Use it only when building a fully custom form UI from scratch.
Signature
import { useFormState } from '@saastro/forms';
const {
currentStepId,
loading,
submitted,
error,
methods,
nextStep,
prevStep,
handleSubmit,
validateAndNext,
isLast,
getSuccessMessage,
getErrorMessage,
resetError,
stepHistory,
} = useFormState({ config, fields, steps });
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
config | FormConfig | Yes | The form configuration (from FormBuilder.build()) |
fields | Fields | Yes | Field definitions map |
steps | Steps | No | Step definitions. If omitted, a single default step with all fields is created |
Return Value
| Property | Type | Description |
|---|---|---|
currentStepId | string | Active step ID |
loading | boolean | true while submitting |
submitted | boolean | true after successful submission |
error | Error | null | Last submission error |
methods | UseFormReturn | React Hook Form instance (register, control, watch, etc.) |
nextStep | (values) => boolean | Navigate to the next step |
prevStep | () => boolean | Navigate to the previous step |
handleSubmit | (data) => Promise<void> | Submit handler (runs plugin hooks + actions) |
validateAndNext | (e?) => Promise<void> | Validate current step, then navigate or submit |
isLast | boolean | Whether the current step is the last one |
getSuccessMessage | () => string | Resolve the success message |
getErrorMessage | () => string | Resolve the error message |
resetError | () => void | Clear the error state |
stepHistory | string[] | Stack of visited step IDs |
What It Does Internally
- Plugin config transform — If a
pluginManagerexists, runstransformConfig()before anything else - Schema compilation — For each field, converts
ValidationRulesor raw Zod schemas into a unifiedz.object(). Chains anycustomValidatorsassuperRefinecalls - Default values — Assigns per-type defaults:
falsefor checkbox/switch,[]for groups,''for text fields, overridden byfield.value - React Hook Form — Creates a
useForminstance withzodResolver(schema) - Plugin lifecycle — Fires
onFormIniton mount, subscribesonFieldChangeviawatch() - Step orchestration — Delegates to
useFormSteps,useFormValidation,useFormSubmit - validateAndNext — Validates current step fields, then calls
nextStep()with conditional routing. FiresonStepChangehooks. On the last step, submits instead
Example: Custom Form UI
import { useFormState } from '@saastro/forms';
import { FormProvider } from 'react-hook-form';
function CustomForm({ config }) {
const {
methods,
currentStepId,
loading,
submitted,
error,
validateAndNext,
prevStep,
isLast,
stepHistory,
getSuccessMessage,
} = useFormState({
config,
fields: config.fields,
steps: config.steps,
});
if (submitted) return <p>{getSuccessMessage()}</p>;
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(() => validateAndNext())}>
<p>Step: {currentStepId}</p>
{/* Render fields for current step */}
{config.steps[currentStepId].fields.map((name) => (
<input key={name} {...methods.register(name)} placeholder={name} />
))}
{stepHistory.length > 0 && (
<button type="button" onClick={prevStep}>
Back
</button>
)}
<button type="submit" disabled={loading}>
{isLast ? 'Submit' : 'Next'}
</button>
{error && <p>{error.message}</p>}
</form>
</FormProvider>
);
}
Related
- useFormSubmit — The submit lifecycle this hook delegates to
- useFormSteps — Step navigation logic
- useFormValidation — Per-step field validation