npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gooonzick/wizard-state

v1.0.0

Published

A state management layer for multi-step wizards. Provides fine-grained subscription channels for efficient React component rendering when using `useSyncExternalStore`.

Readme

@gooonzick/wizard-state

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-core

Basic 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)
  • "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 });

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 useSyncExternalStore stability
  • 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

See Also