Skip to content

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

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

React + TanStack Router integration demo for the XMachines Play architecture with actor-authoritative routing.

What This Demonstrates

  • Shared auth machine reused without framework-specific business logic
  • PlayRouterProvider renderer-based integration with TanStack Router
  • Shell-driven rendering via PlayRenderer with actor-authoritative navigation
  • Non-browser invariant tests plus browser E2E coverage

Running the Demo

From the repository root:

Terminal window
npm install
npm run dev -w @xmachines/play-tanstack-react-router-demo

Then open http://localhost:3000.

Step-by-Step Code Flow

Use this order to understand the implementation:

  1. src/main.tsx mounts <App />.
  2. src/App.tsx calls useMemo(createAppRuntime, []) to create the actor from the shared machine and start it once per mounted app.
  3. src/runtime.ts exports createPlayer (via definePlayer), routeMap (via createRouteMap(authMachine)), and createRegistryResult which binds UI components and actor event actions.
  4. createAppRuntime() constructs a TanStack createRootRoute/createRouter instance, then PlayRouterProvider bridges it to the actor and renders Shell via renderer(actor, router).
  5. Shell renders PlayRenderer, header/nav, and debug panel from actor state.
  6. Tests in test/ and test/browser/ validate invariant and runtime behavior.
// src/App.tsx (shape)
function createAppRuntime() {
const actor = createPlayer();
actor.start();
const registryResult = createRegistryResult(actor);
const rootRoute = createRootRoute();
const router = createRouter({ routeTree: rootRoute, defaultPreload: "intent" });
return { actor, router, registryResult };
}
const { actor, router, registryResult } = useMemo(createAppRuntime, []);
return (
<PlayRouterProvider
actor={actor}
router={router}
routeMap={routeMap}
renderer={(currentActor, currentRouter) => (
<Shell actor={currentActor} router={currentRouter} registryResult={registryResult} />
)}
/>
);
// src/runtime.ts (shape)
export const createPlayer = definePlayer({ machine: authMachine });
export const routeMap = createRouteMap(authMachine);
export function createRegistryResult(actor) {
return defineRegistry(authCatalog, {
actions: {
login: async (args) => actor.send({ type: "auth.login", username: args.username }),
logout: async () => actor.send({ type: "auth.logout" }),
route: async (args) => actor.send({ type: "play.route", to: args.to, ...args.params }),
},
});
}

Key Files

  • src/main.tsx - React entry point that mounts <App />
  • src/App.tsx - actor lifecycle, provider wiring, and Shell rendering
  • src/runtime.ts - actor factory, route map creation, and registry/action binding
  • test/actor-authority.test.ts - actor authority and guarded navigation behavior
  • test/passive-infra.test.ts - passive infrastructure separation contracts
  • test/strict-separation.test.ts - machine/view infrastructure separation contracts
  • test/signal-only.test.ts - signal-only reactivity invariant
  • test/route-parameters.test.ts - route parameter handling
  • test/invalid-route-redirect.test.ts - invalid route redirect behavior
  • test/xstate-route-events.test.ts - XState route event contracts
  • test/browser/shared-demo.browser.test.tsx - canonical browser auth flow
  • test/browser/auth-flow.browser.test.tsx - extended browser auth flow checks
  • test/browser/back-forward-sync.browser.test.tsx - URL sync and history behavior
  • test/browser/dashboard-logout.browser.test.tsx - logout and redirection flow
  • test/browser/guard-rejection.browser.test.tsx - guard-driven rejection in browser
  • test/browser/login-flow.browser.test.tsx - login flow browser coverage
  • test/browser/navigation.browser.test.tsx - navigation behavior in browser
  • test/browser/settings-parameter.browser.test.tsx - parameterized route in browser
  • test/browser/invalid-route-redirect.browser.test.tsx - invalid route redirect in browser
  • test/browser/tanstack-integration.browser.test.tsx - TanStack Router integration checks

State Machine & Architecture Details

The demo utilizes XMachines architectural invariants:

  1. Actor Authority: When a user navigates to a protected route, TanStack Router updates the URL. The PlayRouterProvider intercepts this, translates it to a play.route event, and sends it to the actor. The actor evaluates guards (e.g. isAuthenticated) and executes state transitions.
  2. Passive Infrastructure: The router does not execute route loaders or guards for business logic. The actor dictates whether navigation is permitted.
  3. Signal-Only Reactivity: The bridge leverages React’s internal mechanisms combined with TanStack’s router.subscribe to react precisely when signals update, without relying on useEffect synchronization inside the application codebase.

Watcher Lifecycle and Cleanup Contract

This demo follows the canonical watcher lifecycle:

  1. notify
  2. queueMicrotask
  3. getPending()
  4. Read actor signals and trigger React state updates
  5. Re-arm with watch()/watch(...signals)

Watcher notifications are one-shot. Cleanup is explicit and lifecycle-bound: the provider automatically triggers bridge.disconnect() when the component tree unmounts to prevent memory leaks and zombie subscriptions.

Adapter Boundaries

The TanStack React adapter wraps TanStack’s router.navigate({ to }) and router.subscribe methods while delegating core synchronization policy to RouterBridgeBase.

Available Scripts

These commands are defined in package.json:

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

Verification

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

Terminal window
npm run test -w @xmachines/play-tanstack-react-router-demo
npm run test:browser -w @xmachines/play-tanstack-react-router-demo

Expected result: invariant test suite and the browser shared-demo suite both pass, confirming actor authority, route sync, and guard-driven redirection.

Learn More