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

machina-react

v0.2.0

Published

React external-store hooks for machina FSM instances.

Readme

machina-react

React hooks that subscribe components to machina FSMs. A component reads the machine as a snapshot and rerenders when the machine changes.

machina-react does not create or manage FSM instances. The instance belongs to your app, at module scope or in ordinary React context. You pass that instance to these hooks.

The hooks are built on React's useSyncExternalStore, and snapshots stay consistent under concurrent rendering in React 18. The hooks are client-only. Rendering them on the server throws, because useSyncExternalStore requires a server snapshot that these hooks never supply.

Install

npm install machina machina-react
# or
pnpm add machina machina-react

machina >= 8.2.0 and react >= 18 are peer dependencies. The floor is 8.2 because the hook types reuse the types behind core's input payload maps and event maps.

Quick Start

import { createFsm } from "machina";
import { useFsm } from "machina-react";

const checkout = createFsm({
    id: "checkout",
    initialState: "cart",
    context: { total: 0 },
    states: {
        cart: { submit: "payment" },
        payment: { back: "cart", confirm: "complete" },
        complete: {},
    },
});

const CheckoutStep = () => {
    const { state, handle, canHandle } = useFsm(checkout);

    return (
        <button disabled={!canHandle("submit")} onClick={() => handle("submit")}>
            {state}
        </button>
    );
};

The component rerenders when the FSM settles after an input.

Hooks

The exports split into snapshot hooks, which rerender your component, and event hooks, which run callbacks.

| Export | What it does | | ----------------------------------------------------------- | ---------------------------------------------------------------------------- | | useFsm(fsm, options?) | Snapshot hook. Subscribes a component to an Fsm and rerenders it on change | | useBehavioralFsm(fsm, client, options?) | Snapshot hook. Subscribes to one client of a BehavioralFsm | | useFsmSelector(fsm, selector, options?) | Snapshot hook. Rerenders only when the selected slice changes | | useBehavioralFsmSelector(fsm, client, selector, options?) | Snapshot hook. The selector variant for one client | | useFsmEvent(fsm, eventName, callback) | Event hook. Runs a callback on an FSM event without causing a rerender | | useBehavioralFsmEvent(fsm, client, eventName, callback) | Event hook. The same, filtered to one client | | shallowEqual | Equality helper for selectors that return object literals |

Snapshot Hooks

useFsm(fsm) returns a settled snapshot, one taken after an input and its transitions finish processing, with:

  • state
  • compositeState
  • context, as the live machina context reference
  • handle(inputName, ...args)
  • canHandle(inputName)

Under an FSM with an input payload map, the snapshot's handle enforces the declared payloads, with the same checking core's handle applies.

useBehavioralFsm(fsm, client) returns the matching snapshot shape for a behavioral client, with client in place of context. The snapshot's handle is bound to the client. A client the FSM hasn't handled yet reports state: undefined and compositeState: "" until machina initializes it.

import { useBehavioralFsm } from "machina-react";

// sessionFsm = createBehavioralFsm<Session>()({ ... }) at module scope
const SessionBadge = ({ session }: { session: Session }) => {
    const { state, handle } = useBehavioralFsm(sessionFsm, session);

    return <button onClick={() => handle("refresh")}>{state}</button>;
};

Render Triggers

Snapshot hooks default to rerenderOn: "settled".

useFsm(fsm, { rerenderOn: "settled" });

Modes:

  • "settled" subscribes to handled and transitioned, then coalesces synchronous event bursts with queueMicrotask() so React renders only the final snapshot.
  • "transitioned" rerenders only after transitions.
  • "handled" rerenders only after handled inputs.
  • "all" subscribes to both and notifies immediately.

Selectors

import { shallowEqual, useFsmSelector } from "machina-react";

const state = useFsmSelector(fsm, snapshot => snapshot.state);
const summary = useFsmSelector(
    fsm,
    snapshot => ({
        state: snapshot.state,
        total: snapshot.context.total,
    }),
    { isEqual: shallowEqual }
);

Select primitives by default. For multiple fields, return a small object literal and pass shallowEqual. Avoid selecting mutated context or client sub-objects directly because their references may not change.

useBehavioralFsmSelector(fsm, client, selector, options?) is the behavioral variant:

const state = useBehavioralFsmSelector(sessionFsm, session, snapshot => snapshot.state);

Event Hooks

import { useBehavioralFsmEvent, useFsmEvent } from "machina-react";

useFsmEvent(fsm, "transitioned", payload => {
    console.log(payload.toState);
});

useFsmEvent(fsm, "*", (eventName, payload) => {
    console.log(eventName, payload);
});

useBehavioralFsmEvent(fsm, client, "handled", payload => {
    console.log(payload.client, payload.inputName);
});

Event hooks subscribe in an effect and read the latest callback through a ref. They do not update React state internally, so events do not cause renders unless your callback does.

Custom events subscribe by name too. Under an FSM that declares an event map (createFsm<{ events: MyEvents }>()), the hook accepts the declared names and types each payload, and an undeclared name is a compile error. Without a map, any name is accepted and the payload is unknown.

useFsmEvent(light, "cycleCompleted", ({ cycles }) => {
    console.log(cycles); // number, from the declared map
});

Behavioral event hooks filter built-in client-bearing payloads to the matching client. Wildcard subscriptions also receive clientless custom events as global events.

App Context

Wrap FSM instances with ordinary React context when you want a provider:

const CheckoutFsmContext = createContext<CheckoutFsm | null>(null);

Store the FSM instance in context, then call useFsm(fsm) or useFsmSelector(fsm, selector) in app hooks.

V1 Non-Goals

  • Context factory helpers such as createFsmContext and createBehavioralFsmContext
  • SSR support via getServerSnapshot
  • Devtools or inspector integration

See Also

License

MIT