Complete documentation of every public export from
@epochflow/coreand@epochflow/react.
function createFormFlow<T>(config: FlowConfig<T>): FormFlow<T>Creates a typed, multi-step form flow with validation, persistence, and optional mutation support.
Example:
const flow = createFormFlow({
schema: MySchema,
steps: {
personal: ['firstName', 'lastName', 'email'],
address: ['street', 'city', 'state'],
review: [],
},
persist: { key: 'my-draft', debounceMs: 1000 },
mutation: trpc.leads.create,
})interface FlowConfig<T> {
schema: z.ZodType<T>
steps: Record<string, (keyof T)[]>
persist?: { key: string; debounceMs?: number }
mutation?: (input: T) => Promise<unknown>
}| Property | Required | Description |
|---|---|---|
schema |
✅ | Zod schema defining the shape and validation rules |
steps |
✅ | Maps step names → arrays of field names |
persist |
❌ | Configures localStorage draft persistence |
mutation |
❌ | Async function called when submit() succeeds validation |
interface FlowState<T = unknown> {
values: Partial<T>
errors: Record<string, string[]>
currentStep: string
visitedSteps: string[]
isDirty: boolean
isSubmitting: boolean
isValidating: boolean
submitError: Error | null
data: unknown | null
}The reactive state shape held by the internal store. React hooks return slices of this state.
interface FormFlow<T> {
config: FlowConfig<T>
store: ReturnType<typeof createStore<T>>
stateMachine: ReturnType<typeof createStateMachine>
validator: ValidationAdapter<T>
persistEngine: PersistEngine | null
stepNames: string[]
next: () => string | null
back: () => string | null
goTo: (stepName: string) => string | null
canNext: () => boolean
canBack: () => boolean
canSubmit: () => boolean
validateCurrentStep: () => Record<string, string[]> | null
validateAll: () => Record<string, string[]> | null
setValues: (values: Record<string, unknown>) => void
submit: () => Promise<unknown | null>
}The central instance returned by createFormFlow. You typically access these methods through React hooks rather than calling them directly.
Navigation methods:
| Method | Returns | Description |
|---|---|---|
next() |
string | null |
Advances to the next step; returns new step name or null |
back() |
string | null |
Goes to previous step; returns new step name or null |
goTo(name) |
string | null |
Jumps to named step; returns name or null if invalid |
canNext() |
boolean |
Whether current step is not the last |
canBack() |
boolean |
Whether current step is not the first |
canSubmit() |
boolean |
Whether on the final step |
Validation & mutation:
| Method | Returns | Description |
|---|---|---|
validateCurrentStep() |
Record<string, string[]> | null |
Validates only fields in steps[currentStep] |
validateAll() |
Record<string, string[]> | null |
Validates entire schema against current values |
setValues(partial) |
void |
Merges new values into form state |
submit() |
Promise<unknown | null> |
Validates all, then calls mutation; returns result or null |
function createStore<T>(initialState: FlowState<T>): {
getState: () => FlowState<T>
setState: (partial: Partial<FlowState<T>>) => void
subscribe: (listener: () => void) => () => void
}A minimal reactive store. Used internally by createFormFlow; exposed for advanced use cases.
function createPersistEngine(config: PersistConfig): PersistEngineCreates a localStorage-backed persistence engine.
interface PersistConfig {
key: string
debounceMs?: number
}| Property | Default | Description |
|---|---|---|
key |
— | localStorage key |
debounceMs |
1000 |
Milliseconds to wait before persisting |
interface PersistEngine {
save: (data: unknown) => void
load: () => unknown | null
clear: () => void
connectToStore: <T>(store: {
getState: () => { values: Partial<T> }
subscribe: (listener: () => void) => () => void
}) => () => void
}Handles saving, loading, and clearing persisted draft values. connectToStore auto-wires persistence to a reactive store.
function zodAdapter<T>(schema: z.ZodType<T>): ValidationAdapter<T>Creates a Zod-based validation adapter. Used internally by createFormFlow.
interface ValidationAdapter<T> {
validateStep: (
stepFields: (keyof T)[],
values: Partial<T>
) => Record<string, string[]> | null
validateAll: (values: unknown) => Record<string, string[]> | null
}| Method | Description |
|---|---|
validateStep(fields, values) |
Validates only the given fields against a .pick() of the schema |
validateAll(values) |
Validates the full schema |
Both return null on success, or a Record<string, string[]> of field → error messages on failure.
function createStateMachine(steps: string[]): {
get status(): FlowStatus
get currentStep(): string
canNext: () => boolean
canBack: () => boolean
canSubmit: () => boolean
next: () => string | null
back: () => string | null
goTo: (stepName: string) => string | null
setStatus: (status: FlowStatus) => void
}A linear navigation state machine. Used internally; exposed for framework adapters (Vue, Svelte, etc.).
type FlowStatus = 'idle' | 'validating' | 'navigating' | 'submitting' | 'success' | 'error'High-level status of the form flow.
function FormFlowProvider<T>({
children,
flow,
}: {
children: React.ReactNode
flow: FormFlow<T>
}): JSX.ElementProvides a form flow instance to all descendant components via React context.
Example:
<FormFlowProvider flow={onboardingFlow}>
<App />
</FormFlowProvider>function useFormFlow<T>(): UseFormFlowReturn<T>Primary hook for consuming form state and actions.
Returns:
interface UseFormFlowReturn<T> {
values: Partial<T>
errors: Record<string, string[]>
currentStep: string
visitedSteps: string[]
isDirty: boolean
isSubmitting: boolean
isValidating: boolean
submitError: Error | null
data: unknown | null
next: () => string | null
back: () => string | null
goTo: (stepName: string) => string | null
canNext: boolean
canBack: boolean
canSubmit: boolean
setValues: (values: Record<string, unknown>) => void
validateCurrentStep: () => Record<string, string[]> | null
validateAll: () => Record<string, string[]> | null
submit: () => Promise<unknown | null>
}Important: Always pass your schema type as a generic for full type inference:
const { values } = useFormFlow<OnboardingData>()
// values.workspaceName is typed as string | undefinedfunction useFormFlowContext<T>(): FormFlow<T>Returns the raw FormFlow instance from context. Useful for building custom hooks or framework adapters.
Throws if called outside of a FormFlowProvider.
function useStepFields<T>(stepName: string): UseStepFieldsReturnReturns metadata about a specific step.
interface UseStepFieldsReturn {
fields: string[]
isCurrent: boolean
isVisited: boolean
}Use case: Building a step indicator / progress bar.
function StepIndicator() {
const steps = ['personal', 'address', 'review']
return (
<div>
{steps.map((step) => {
const { isCurrent, isVisited } = useStepFields(step)
return (
<span
key={step}
className={isCurrent ? 'active' : isVisited ? 'completed' : 'pending'}
>
{step}
</span>
)
})}
</div>
)
}function usePersistedDraft<T>(): UsePersistedDraftReturnOpt-in hook for building draft save / restore UI.
interface UsePersistedDraftReturn {
hasDraft: boolean
shouldShowRestorePrompt: boolean
saveDraft: () => void
restoreDraft: () => void
clearDraft: () => void
}Only works when persist is configured in createFormFlow. The store auto-persists on every value change; this hook exposes manual controls, the hasDraft flag, and shouldShowRestorePrompt (whether to show a restore affordance for the current provider mount—false after a successful restoreDraft, even when hasDraft stays true).
Example:
function DraftBanner() {
const { shouldShowRestorePrompt, restoreDraft, clearDraft } = usePersistedDraft()
if (!shouldShowRestorePrompt) return null
return (
<div>
<p>Draft found</p>
<button onClick={restoreDraft}>Restore</button>
<button onClick={clearDraft}>Dismiss</button>
</div>
)
}Epoch Flow's type safety flows from your Zod schema through every layer:
Zod Schema
↓
createFormFlow<T>({ schema, steps, ... })
↓
FormFlowProvider flow={flow}
↓
useFormFlow<YourType>() → typed values, errors, submit, etc.
If you skip the generic (useFormFlow() without <YourType>), values will be Partial<unknown> and you'll lose autocomplete and type checking.