@marianmeres/wizard
v2.0.4
Published
[](https://www.npmjs.com/package/@marianmeres/wizard) [](https://jsr.io/@marianmeres/wizard) [ - Global context shared across all steps
- Full TypeScript support with generics
Example usage
import { createWizard } from "@marianmeres/wizard";
// Define your data and context types for full type safety
interface StepData {
name?: string;
email?: string;
agreed?: boolean;
}
interface Context {
apiUrl: string;
}
const wizard = createWizard<StepData, Context>("registration", {
steps: [
{ label: "Personal Info", data: { name: "", email: "" } },
{
label: "Terms & Conditions",
canGoNext: false, // must be explicitly enabled
preNext: async (data, { update, context }) => {
// Validate before proceeding
if (!data.agreed) {
throw new Error("You must agree to the terms");
}
update({ canGoNext: true });
},
prePrevious: async (_data, { update }) => {
// Reset validation state when going back
update({ canGoNext: false, data: { agreed: false } });
},
},
{ label: "Confirmation" },
],
context: { apiUrl: "https://api.example.com" },
onDone: async ({ steps, context }) => {
// Called when next() is invoked on the last step
const formData = steps.map((s) => s.data);
console.log("Submitting to", context.apiUrl, formData);
},
});
// Subscribe to state changes
wizard.subscribe(({ step, steps, inProgress }) => {
// Step properties
const { label, index, data, canGoNext, error, isFirst, isLast } = step;
// Update step state
step.update({ data: { name: "John" } });
step.update({ canGoNext: true });
step.update({ error: null });
// Functional updates
step.update({ data: (prev) => ({ ...prev, email: "[email protected]" }) });
});
// Wizard navigation API
await wizard.next(); // Move to next step
await wizard.next({ name: "John" }); // Move with data merge
await wizard.previous(); // Move to previous step
await wizard.reset(); // Reset to initial state
await wizard.goto(2); // Jump to step index 2
await wizard.goto(2, [null, { agreed: true }]); // Jump with step data
// Utility methods
wizard.allowCanGoNext(); // Enable free navigation
wizard.resetCanGoNext(); // Restore initial canGoNext values
wizard.publish(); // Force state emission
// Access context and label
wizard.context; // { apiUrl: "https://api.example.com" }
wizard.label; // "registration"API Overview
createWizard<TData, TContext>(label, options)
Creates a wizard instance with full TypeScript support.
Key options:
steps- Array of step configurations (minimum 2 required)context- Global context object (optional)onDone- Callback when completing the last step
Navigation methods:
next(data?)- Move to next stepprevious()- Move to previous stepreset()- Reset to initial stategoto(index, stepsData?, assert?)- Jump to specific step
Utilities:
allowCanGoNext()/resetCanGoNext()- Control navigation flagssubscribe(callback)- React to state changesget()- Get current state synchronously
For complete API documentation including all types and interfaces, see API.md.
Hook Safety
Navigation methods (next(), previous(), goto(), reset()) cannot be called from
inside pre-hooks. Attempting to do so will throw a TypeError.
// ❌ This will throw TypeError
const wizard = createWizard("foo", {
steps: [
{
label: "one",
preNext: async (_data, { wizard }) => {
await wizard.next(); // TypeError: Cannot call next() from inside pre-hooks
},
},
{ label: "two" },
],
onDone: async () => {},
});Safe operations inside hooks
update()- Modify step state (data, error, canGoNext)wizard.get()- Read current statewizard.context- Access/modify contextwizard.label- Read wizard labelwizard.allowCanGoNext()- Enable free navigationwizard.resetCanGoNext()- Restore initial canGoNext valueswizard.publish()- Force state emission
Deferred navigation
If you need to trigger navigation based on hook logic, defer it using setTimeout:
preNext: (async (_data, { wizard }) => {
// Deferred navigation (runs after hook completes)
setTimeout(() => wizard.reset(), 0);
});Migration from v1.x
// v1.x
import { createWizardStore } from "@marianmeres/wizard";
const wizard = createWizardStore("foo", { ... });
step.set({ canGoNext: true });
await step.next(data);
// v2.x
import { createWizard } from "@marianmeres/wizard";
const wizard = createWizard<MyData, MyContext>("foo", { ... });
step.update({ canGoNext: true });
await wizard.next(data); // navigation only via wizard, not stepKey changes:
createWizardStore→createWizardstep.set()→step.update()step.next()/step.previous()removed (usewizard.next()/wizard.previous())setin hook context →update- Full generic type support
License
MIT
