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

@xmachines/play-react

v2.1.1

Published

React renderer for XMachines Play architecture with signal-driven rendering

Readme

@xmachines/play-react

React renderer for XMachines Play architecture with signal-driven rendering.

License: MIT Version

Installation

pnpm add @xmachines/play-react

Peer dependencies. Install them separately:

pnpm add react react-dom xstate @xstate/store @xmachines/json-render-react @xmachines/json-render-core @xmachines/json-render-xstate

Supported versions:

  • react / react-dom: ^18.0.0 || ^19.0.0
  • xstate: ^5.31.0
  • @xstate/store: ^3.17.0
  • @xmachines/json-render-*: ^0.20.0-xm.2

Usage

Standard usage — PlayUIProvider + PlayRenderer

The recommended pattern for actor-driven React rendering:

import { PlayUIProvider, PlayRenderer, defineRegistry } from "@xmachines/play-react";
import { definePlayer } from "@xmachines/play-xstate";
import { myMachine } from "./machine.js"; // your xstate machine (states carry meta.view specs)
import { myCatalog } from "./catalog.js"; // defineCatalog(schema, ...) result, using the schema from "@xmachines/json-render-react/schema"
import { Login, Dashboard } from "./components.js"; // your React components

// 1. Create and start the actor
const actor = definePlayer({ machine: myMachine })();
actor.start();

// 2. Define the component registry with action handlers
const registryResult = defineRegistry(myCatalog, {
	components: { Login, Dashboard },
	actions: {
		login: async ({ username }) => actor.send({ type: "auth.login", username }),
		logout: async () => actor.send({ type: "auth.logout" }),
	},
});

// 3. Render — signals drive view transitions automatically
function App() {
	return (
		<PlayUIProvider actor={actor} registryResult={registryResult}>
			<PlayRenderer />
		</PlayUIProvider>
	);
}

With optional JSONUIProvider props

Pass navigation and validation helpers through PlayUIProvider:

// actor, registryResult from the Quick Start above
<PlayUIProvider
	actor={actor}
	registryResult={registryResult}
	navigate={(path) => history.pushState(null, "", path)}
	validationFunctions={{ isEmail: (v) => /^.+@.+$/.test(String(v)) }}
>
	<PlayRenderer />
</PlayUIProvider>

Custom provider composition

Use ActorProvider directly when you need to compose providers manually:

import type { ReactNode } from "react";
import { ActorProvider, JSONUIProvider, PlayRenderer, usePlayView } from "@xmachines/play-react";

// Handlers and store live in ViewContext, so an inner bridge component must
// read them via usePlayView() and forward all three to JSONUIProvider —
// passing only `registry` would drop the action handlers and create a fresh store.
function Bridge({ children }: { children: ReactNode }) {
	const view = usePlayView();
	return (
		<JSONUIProvider registry={view.registry} handlers={view.handlers} store={view.store}>
			{children}
		</JSONUIProvider>
	);
}

// actor, registryResult from the Quick Start above
<ActorProvider actor={actor} registryResult={registryResult}>
	<Bridge>
		<PlayRenderer />
	</Bridge>
</ActorProvider>;

Accessing the actor from inside the tree

import { useActor } from "@xmachines/play-react";

function SubmitButton() {
	const actor = useActor();
	return <button onClick={() => actor.send({ type: "SUBMIT" })}>Submit</button>;
}

Subscribing to signals directly

import { useState } from "react";
import { useSignalEffect } from "@xmachines/play-react";

function MyComponent({ actor }) {
	const [view, setView] = useState(null);

	useSignalEffect(() => {
		setView(actor.currentView.get());
	}, [actor]); // deps: re-subscribe when the actor prop swaps

	return <div>{view?.root}</div>;
}

API Summary

Components

| Export | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | <PlayUIProvider> | Composite provider. It wraps ActorProvider and JSONUIProvider. Use it as the standard entry point. | | <PlayRenderer> | Zero-prop leaf component. Reads the current actor view from context and renders it. Must be inside PlayUIProvider or ActorProvider. | | <ActorProvider> | The low-level provider. It owns the actor bridge, the signal subscription, and the StateStore lifecycle of each view. | | <PlayErrorBoundary> | The React class error boundary that catches a render error of a catalog component. |

Hooks

| Export | Description | | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | useSignalEffect(callback, deps?) | Subscribes to the TC39 signal changes. It runs the callback again when a signal that the callback reads changes, and the callback triggers the re-render with its own setState. The optional deps array creates the subscription again, like useEffect. The hook removes the subscription on unmount. | | useActor() | Returns the raw actor instance. Must be called inside an ActorProvider/PlayUIProvider tree. | | usePlayView() | Returns { spec, handlers, registry, store } for the current view. Must be called inside an ActorProvider/PlayUIProvider tree. |

Types

| Export | Description | | ------------------------ | -------------------------------------------------------------------------------------------------- | | PlayUIProviderProps | Props for <PlayUIProvider> | | ActorProviderProps | Props for <ActorProvider> (also exported as PlayRendererProps for migration compatibility) | | PlayErrorBoundaryProps | Props for <PlayErrorBoundary> | | PlayErrorBoundaryState | State shape for <PlayErrorBoundary> | | AnyPlayActor | Type alias for AbstractActor<AnyActorLogic> — the bare actor type that the context providers use | | ViewContextValue | The value shape that usePlayView() returns | | RenderErrorHandler | Error handler callback type for render errors |

Re-exports from @xmachines/json-render-react

@xmachines/play-react re-exports the complete @xmachines/json-render-react surface, so a consumer needs one import only:

import {
	defineRegistry,
	useBoundProp,
	JSONUIProvider,
	StateProvider,
	ActionProvider,
	VisibilityProvider,
	ValidationProvider,
	Renderer,
} from "@xmachines/play-react";

Key Principle

React state is never the place for the business logic. It only triggers the render cycle of React. The signals (@xmachines/play-signals) are the source of truth. PlayUIProvider observes the actor signals with useSignalEffect, and it renders again when the current view changes. It groups rapid signal updates into microtasks, so React does not render more often than necessary.

Testing

Run unit tests (jsdom environment):

pnpm --filter @xmachines/play-react test

Run tests with coverage:

pnpm --filter @xmachines/play-react run test:coverage

Run the browser integration tests. They require Chromium:

pnpm --filter @xmachines/play-react run test:browser

Coverage thresholds: 80% lines, functions, branches, and statements.

License

MIT — see LICENSE.