useFormStepsInfo
Computes rich metadata for every step — status, error counts, completion, progress percentage. Designed to power UI components like progress bars, tab navigators, and step indicators.
If you don’t want to build your own UI, the package ships three prebuilt components on top of this hook:
StepsProgress,StepsNavigation, andStepsAccordion— all take the samesteps,currentStepId,stepHistory, andfieldsprops. (StepsAccordionadditionally requires Accordion components in your component registry — see Components.)
Signature
import { useFormStepsInfo, type FormStepsInfo, type StepInfo, type StepStatus } from '@saastro/forms';
const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });
Must be called inside a react-hook-form
FormProvider(it reads error state viauseFormContext()). Anywhere inside<Form>’s children this is automatic. If you driveuseFormStateyourself, wrap your tree in<FormProvider {...methods}>(fromreact-hook-form) first.
Parameters
| Parameter | Type | Description |
|---|---|---|
steps | Steps | Steps configuration map (Record<string, Step>) |
currentStepId | string | Active step ID |
stepHistory | string[] | Stack of visited step IDs (excludes the current step) |
fields | Fields | Field definitions map (Record<string, FieldConfig>) |
These values typically come from useFormState: currentStepId and stepHistory are returned directly, and steps/fields are read from the resolved config it returns (config.steps, config.fields).
Return Value: FormStepsInfo
| Property | Type | Description |
|---|---|---|
steps | StepInfo[] | Metadata for every step (see below) |
currentStepId | string | Active step ID |
totalSteps | number | Total number of steps |
completedSteps | number | Count of completed steps |
progress | number | Completion percentage (0-100) |
stepHistory | string[] | Stack of visited step IDs |
canGoNext | boolean | Heuristic: true when there is step history or more than one step (it does not check whether a next step actually exists from the current one) |
canGoPrev | boolean | Whether back navigation is possible (stepHistory is non-empty) |
getStepInfo(id) | (id: string) => StepInfo | undefined | Get info for a specific step (undefined for unknown IDs) |
getStepStatus(id) | (id: string) => StepStatus | Get status for a specific step ('pending' for unknown IDs) |
StepInfo
| Property | Type | Description |
|---|---|---|
id | string | Step ID |
fields | string[] | Field names in this step |
fieldCount | number | Number of fields |
status | StepStatus | 'pending', 'current', 'completed', or 'error' |
hasErrors | boolean | Whether any fields have validation errors |
errorCount | number | Number of fields with errors |
isVisited | boolean | Whether the step has been visited |
isCurrent | boolean | Whether this is the active step |
isCompleted | boolean | Visited, not current, no errors, all fields valid |
canNavigate | boolean | Whether the user can navigate to this step |
StepStatus
type StepStatus = 'pending' | 'current' | 'completed' | 'error';
'pending'— Not yet visited'current'— Active step with no validation errors (an active step with errors reports'error')'completed'— Visited, not current, no errors, all fields valid'error'— Has validation errors (current or visited)
Reactivity note: the result is memoized and reads field values non-reactively (via
getValues()). It refreshes when the current step, the step history, or validation errors change — not on every keystroke.
Example: Custom Progress Bar
import { useFormStepsInfo, type Fields, type Steps } from '@saastro/forms';
interface StepProgressProps {
steps: Steps;
currentStepId: string;
stepHistory: string[];
fields: Fields;
}
function StepProgress({ steps, currentStepId, stepHistory, fields }: StepProgressProps) {
const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });
return (
<div>
<div className="flex gap-2 mb-4">
{info.steps.map((step) => (
<div
key={step.id}
className={`flex-1 h-2 rounded ${
step.isCompleted
? 'bg-green-500'
: step.isCurrent
? 'bg-blue-500'
: step.hasErrors
? 'bg-red-500'
: 'bg-gray-200'
}`}
/>
))}
</div>
<p className="text-sm text-muted-foreground">
Step {info.steps.findIndex((s) => s.isCurrent) + 1} of {info.totalSteps} ({info.progress}%
complete)
</p>
</div>
);
}
Example: Step Navigator with Error Badges
import { useFormStepsInfo, type Fields, type Steps } from '@saastro/forms';
interface StepNavProps {
steps: Steps;
currentStepId: string;
stepHistory: string[];
fields: Fields;
onStepClick: (stepId: string) => void;
}
function StepNav({ steps, currentStepId, stepHistory, fields, onStepClick }: StepNavProps) {
const info = useFormStepsInfo({ steps, currentStepId, stepHistory, fields });
return (
<nav className="flex gap-1">
{info.steps.map((step, i) => (
<button
key={step.id}
onClick={() => step.canNavigate && onStepClick(step.id)}
disabled={!step.canNavigate}
className={`px-3 py-1 rounded text-sm ${
step.isCurrent
? 'bg-primary text-white'
: step.isCompleted
? 'bg-green-100 text-green-800'
: 'bg-gray-100 text-gray-500'
}`}
>
{i + 1}. {step.id}
{step.hasErrors && <span className="ml-1 text-xs text-red-500">({step.errorCount})</span>}
</button>
))}
</nav>
);
}
Related
- Multi-Step Forms — Full guide on step configuration, branching, and navigation
- useFormState — The form engine that provides
currentStepId,stepHistory, and the resolved config