@gooonzick/wizard-state
v1.12.0
Published
State management adapter for the WizardForm framework, shared by the React, Vue, Svelte and Solid bindings
Maintainers
Readme
@gooonzick/wizard-state
Internal adapter package — used by @gooonzick/wizard-react and @gooonzick/wizard-vue. Most applications should depend on the React/Vue packages only; import WizardStateManager from those packages when needed for advanced integrations.
A state management layer for multi-step wizards. Provides fine-grained subscription channels for efficient React component rendering when using useSyncExternalStore.
Overview
WizardStateManager wraps a WizardMachine instance and adds a subscription-based state notification system. This enables React hooks to subscribe to specific state channels (state, navigation, validation, loading) for selective re-renders, avoiding unnecessary updates.
Installation
pnpm add @gooonzick/wizard-state @gooonzick/wizard-coreBasic Usage
import { WizardMachine } from "@gooonzick/wizard-core";
import { WizardStateManager } from "@gooonzick/wizard-state";
// Create a wizard machine
const machine = new WizardMachine(wizardDefinition, context, initialData);
// Wrap it with the state manager
const manager = new WizardStateManager(machine, "step-1");
// Subscribe to state changes
const unsubscribe = manager.subscribe(() => {
console.log("State changed");
});
// Subscribe to specific channel
const navUnsubscribe = manager.subscribe(
() => {
console.log("Navigation state changed");
},
"navigation"
);
// Handle machine state changes
manager.handleStateChange(newState, oldState);
// Clean up
unsubscribe();
navUnsubscribe();API
Constructor
new WizardStateManager<T extends WizardData>(
machine: WizardMachine<T>,
initialStepId: StepId
)Creates a state manager wrapping the provided machine.
Subscription Methods
subscribe(listener, channel?)
Subscribes to state changes on a specific channel or all channels.
// Subscribe to all channels (default)
const unsub = manager.subscribe(() => console.log("Any change"));
// Subscribe to specific channel
const navUnsub = manager.subscribe(
() => console.log("Navigation changed"),
"navigation"
);Channels:
"state"- Data or current step changes"navigation"- Navigation capabilities (canGoNext, canGoPrevious)"validation"- Validation state or errors"loading"- Loading flags (isValidating, isSubmitting, isNavigating, isLoadingStep)"all"- All channels (default)
Snapshot Methods
getStateSnapshot()
Returns cached state snapshot for the current step and data.
const snapshot = manager.getStateSnapshot();
console.log(snapshot.currentStepId, snapshot.data);getNavigationSnapshot()
Returns cached navigation state (async-computed).
const navSnap = manager.getNavigationSnapshot();
console.log(navSnap.canGoNext, navSnap.canGoPrevious);getValidationSnapshot()
Returns cached validation state.
const validSnap = manager.getValidationSnapshot();
console.log(validSnap.isValid, validSnap.validationErrors);getLoadingSnapshot()
Returns cached loading state.
const loadingSnap = manager.getLoadingSnapshot();
console.log(loadingSnap.isValidating, loadingSnap.isSubmitting);State Management
handleStateChange(newState, oldState)
Called when the machine state changes. Automatically determines which channels to notify based on what actually changed.
machine.subscribe((newState, oldState) => {
manager.handleStateChange(newState, oldState);
});setLoadingState(update)
Updates loading state and notifies loading channel subscribers.
manager.setLoadingState({ isValidating: true });isLoadingStep (WIZ-013) is different from the other flags: it mirrors machine.snapshot.isLoadingStep and is seeded from the snapshot, so trackLoading() only accepts TrackedLoadingFlag ("isValidating" | "isSubmitting" | "isNavigating") — trackLoading("isLoadingStep") is a type error. setLoadingState() likewise only accepts the tracked flags (isLoadingStep is a type error). When the machine replaces the current step's definition without any other state change (a background preloadStep() / validateAll() of the current step), the manager refreshes the "state" slice's currentStep. createWizardActions adds preloadStep(stepId), which prefetches a lazy step without touching any loading flag and never rejects (failures are reported by the navigation that needs the step; machine.preloadStep() itself still rejects).
Notification
notifySubscribers(channels)
Manually notify subscribers of specific channels.
manager.notifySubscribers(["state", "navigation"]);Machine Access
getMachine()
Get direct access to the underlying machine.
const machine = manager.getMachine();getCurrentStep()
Get the current step definition.
const step = manager.getCurrentStep();getVisitedSteps()
Get list of visited step IDs.
const visited = manager.getVisitedSteps();getStepHistory()
Get ordered history of step navigation.
const history = manager.getStepHistory();Design Philosophy
- Cached Snapshots - All snapshot getters return the same reference until the underlying data actually changes, enabling
useSyncExternalStorestability - Channel-Based Subscriptions - Listeners only receive notifications for the specific channels they care about, reducing unnecessary React renders
- Async Navigation - Navigation capabilities are computed asynchronously in the background to avoid blocking renders.
canGoNext/isLastStepare seeded synchronously from the machine'sprogress.isLastStep(on creation and on every step change), so the first render is already correct for synchronous graphs
See Also
- @gooonzick/wizard-core - Core state machine
- @gooonzick/wizard-react - React hooks using WizardStateManager
