Skip to content

@xmachines/play-react-router

API / @xmachines/play-react-router

React Router v7 adapter for the XMachines Play Universal Player Architecture — synchronizes actor state with the browser URL bidirectionally using the createBrowserRouter data API.

Part of the XMachines Play monorepo.

License: MIT


Installation

Terminal window
npm install @xmachines/play-react-router

Peer dependencies — install if not already present:

Terminal window
npm install react@"^18 || ^19" react-router@"^7.0.0" xstate@"^5.31.0"

Usage

PlayRouterProvider connects a PlayerActor to React Router inside a React component tree. It creates a ReactRouterBridge on mount, keeps actor state and the browser URL in sync bidirectionally, and tears the bridge down cleanly on unmount.

All three props (actor, router, routeMap) must be stable references. Create them outside of JSX or memoize with useMemo. Recreating them inline triggers a bridge disconnect/reconnect on every render.

import { useMemo, useEffect } from "react";
import { createBrowserRouter, RouterProvider } from "react-router";
import { PlayRouterProvider, createRouteMap } from "@xmachines/play-react-router";
import { definePlayer } from "@xmachines/play-xstate";
import { myMachine } from "./machine.js";
const createPlayer = definePlayer({ machine: myMachine });
const routeMap = createRouteMap(myMachine);
function createAppRuntime() {
const actor = createPlayer();
actor.start();
const router = createBrowserRouter([{ path: "*", element: <App actor={actor} /> }]);
return { actor, router };
}
export default function Root() {
const { actor, router } = useMemo(createAppRuntime, []);
useEffect(() => () => actor.stop(), [actor]);
return (
<PlayRouterProvider
actor={actor}
router={router}
routeMap={routeMap}
renderer={(_, currentRouter) => <RouterProvider router={currentRouter} />}
/>
);
}

ReactRouterBridge — Manual (imperative API)

Use ReactRouterBridge directly when you need imperative lifecycle control outside React.

Requires createBrowserRouter (data router API). The legacy <BrowserRouter> component is not supported — it does not expose the subscribe/navigate API.

import { createBrowserRouter } from "react-router";
import { ReactRouterBridge, createRouteMap } from "@xmachines/play-react-router";
import { myMachine } from "./machine.js";
const router = createBrowserRouter([
/* routes */
]);
const routeMap = createRouteMap(myMachine);
const bridge = new ReactRouterBridge(router, actor, routeMap);
bridge.connect(); // starts bidirectional sync
// ... later:
bridge.disconnect(); // stops sync and cleans up subscriptions

API

PlayRouterProvider

A React component that manages a ReactRouterBridge lifecycle via useEffect.

interface PlayRouterProviderProps<TActor> {
/** The actor to sync with React Router. Must be a stable reference. */
actor: TActor;
/** The React Router instance returned by `createBrowserRouter`. */
router: BrowserRouterInstance;
/**
* Bidirectional route map for state ID ↔ URL path lookups.
* Must be a stable reference — memoize with useMemo if constructed inline.
*/
routeMap: RouteMap;
/** Render callback — receives the actor and router. */
renderer: (actor: TActor, router: BrowserRouterInstance) => ReactNode;
}

ReactRouterBridge

Extends RouterBridgeBase from @xmachines/play-router. Implements the RouterBridge protocol.

MethodDescription
connect()Subscribes to router changes and syncs actor state from the current URL
disconnect()Unsubscribes and stops all synchronization

Types exported from this package

ExportDescription
PlayRouterProviderPropsProps interface for PlayRouterProvider
PlayActorConstraint type for actors accepted by PlayRouterProvider and the bridge

Route map utilities (re-exported from @xmachines/play-router)

ExportDescription
RouteMapBidirectional state ID ↔ URL path map
createRouteMap(machine)Build a RouteMap directly from an XState machine definition
createRouteMapFromTree(tree)Build a RouteMap from a RouteTree object
RouteMapOptionsOptions type for createRouteMap
RouteMappingType for a single { stateId, path } entry
RouterBridgeInterface that ReactRouterBridge satisfies
PlayRouteEventThe play.route event type sent to actors on navigation

Testing

Run tests for this package in isolation:

Terminal window
npm test -w packages/play-react-router

Or from inside the package directory:

Terminal window
npm test

Tests use Vitest. Component tests (*.test.tsx) run in a jsdom environment via @testing-library/react. Unit tests (*.test.ts) run in Node.

Browser tests (test/browser/**/*.browser.test.ts) run against real Chromium via Playwright, covering async sequencing that jsdom cannot faithfully reproduce: BACK/FORWARD navigation, router.subscribe callback ordering, echo suppression under real microtask timing, and subscriber teardown on disconnect().

Terminal window
# Run browser tests only
npx vitest --config vitest.browser.config.ts --project play-react-router-browser

Coverage thresholds (v8 provider):

TypeThreshold
Lines80%
Functions80%
Branches80%
Statements80%

License

MIT

@xmachines/play-react-router

React Router v7 adapter for XMachines Play architecture. Synchronizes browser URL with actor state using createBrowserRouter data API.

Classes

Interfaces

Functions