Skip to content

React Router Demo

Examples / @xmachines/play-react-router-demo

React Router v7 integration demo for the XMachines Play architecture.

What This Demonstrates

  • Shared auth machine reused without framework-specific business logic
  • PlayRouterProvider renderer-based integration with React Router
  • Shell-driven rendering via PlayRenderer with actor-authoritative navigation
  • Canonical TC39 Signals lifecycle mapped to React’s rendering loop
  • Non-browser invariant tests plus browser E2E coverage

Running the Demo

From the repository root:

Terminal window
npm install
npm run dev -w packages/play-react-router/examples/demo

Then open http://localhost:5173.

Step-by-Step Code Flow

Use this order to understand the implementation:

  1. src/main.tsx mounts <App />.
  2. src/App.tsx creates/starts the actor from shared machine + catalog.
  3. Route metadata is extracted from the machine and converted into a route map.
  4. PlayRouterProvider wires createBrowserRouter and actor navigation in both directions.
  5. Shell (inside App.tsx) renders PlayRenderer and actor-driven navigation UI.
  6. Browser tests in test/browser/ validate startup and auth navigation flow.
// src/main.tsx (shape)
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);
// src/App.tsx (shape)
const createPlayer = definePlayer({ machine: authMachine, catalog });
const actor = createPlayer();
actor.start();
const routeTree = extractMachineRoutes(authMachine);
const routeMap = createRouteMapFromTree(routeTree);
return (
<PlayRouterProvider
actor={actor}
router={router}
routeMap={routeMap}
renderer={(currentActor, currentRouter) => {
void currentActor;
return <RouterProvider router={currentRouter} />;
}}
/>
);
// src/components/Login.tsx (shape)
export function Login() {
return (
<button onClick={() => actor.send({ type: "auth.login", username, password })}>
Login
</button>
);
}

Key Files

  • src/main.tsx - React entry point that mounts <App />
  • src/App.tsx - actor lifecycle, route extraction, provider wiring, and renderer composition
  • src/components/ - demo views bound to catalog component keys (Home, Login, Dashboard, Profile, etc.)
  • test/browser/startup.browser.test.tsx - startup assertion for public home + login control
  • test/browser/auth-flow.browser.test.tsx - end-to-end login -> dashboard -> profile -> logout flow

State Machine & Architecture Details

The demo utilizes XMachines architectural invariants:

  1. Actor Authority: Navigation triggers URL changes, which the PlayRouterProvider intercepts and converts into play.route events. The actor evaluates these events against its internal guards and transitions.
  2. Passive Infrastructure: The router does not execute business logic. The actor dictates whether navigation is permitted. The React application only triggers renders.
  3. Signal-Only Reactivity: The bridge leverages React’s useSyncExternalStore (internally within the hooks) to react precisely when signals update, without polluting the React component tree with useState or useEffect for business logic.

Watcher Lifecycle and Cleanup Contract

This demo follows the same watcher lifecycle used across @xmachines/play-signals and framework adapters:

  1. notify
  2. queueMicrotask
  3. Drain pending work with getPending()
  4. Read actor signals and project framework-local state
  5. Re-arm via watch()/watch(...signals)

Watcher notifications are one-shot. Cleanup is explicit: bridge/provider teardown must call disconnect/unwatch paths, never rely on GC-only cleanup. The PlayRouterProvider handles this natively on component unmount.

Adapter Boundaries

PlayRouterProvider and the React Router bridge stay passive infrastructure. Business validity remains actor-owned, while RouterBridgeBase remains the shared policy layer and the concrete React adapter stays a thin port, delegating DOM synchronization to React Router.

Available Scripts

These commands are defined in package.json:

CommandDescription
npm run dev -w packages/play-react-router/examples/demoStart Vite dev server
npm run build -w packages/play-react-router/examples/demoBuild production bundle
npm run preview -w packages/play-react-router/examples/demoPreview built bundle
npm run test -w packages/play-react-router/examples/demoRun Vitest test suite
npm run test:browser -w packages/play-react-router/examples/demoRun browser-focused Vitest suite

Verification

Use these checks to validate README claims against the current demo implementation:

Terminal window
npm run test -w packages/play-react-router/examples/demo
npm run test:browser -w packages/play-react-router/examples/demo

Expected result: tests pass for startup and auth-flow browser scenarios.

Learn More