TanStack React Router Demo
Examples / @xmachines/play-tanstack-react-router-demo
React + TanStack Router demonstration of Play’s actor-authoritative routing and rendering model.
What This Demonstrates
- Shared auth machine reused without framework-specific business logic
PlayRouterProviderrenderer-based integration with TanStack Router- Shell-driven rendering via
PlayRendererwith actor-authoritative navigation - Non-browser invariant tests plus browser E2E coverage
Running the Demo
From this directory (packages/play-tanstack-react-router/examples/demo):
npm installnpm run devOpen http://localhost:3000.
Step-by-Step Code Flow
This demo uses provider-based integration centered on PlayRouterProvider and a route map generated from the machine:
src/main.tsxmounts<App />.src/App.tsxcreates/starts the actor from shared machine + catalog.src/App.tsxbuildsrouteMapfromextractMachineRoutes(authMachine)+createRouteMapFromTree(routeTree).PlayRouterProviderbridges TanStack Router to the actor and rendersShellviarenderer(actor, router).ShellrendersPlayRenderer, header/nav, and debug panel from actor state.- Tests in
test/andtest/browser*/validate invariant and runtime behavior.
// src/App.tsx (shape)const routeTree = extractMachineRoutes(authMachine);const routeMap = createRouteMapFromTree(routeTree);
return ( <PlayRouterProvider actor={actor} router={router} routeMap={routeMap} renderer={(currentActor, currentRouter) => ( <Shell actor={currentActor} router={currentRouter} /> )} />);// src/components/Login.tsx (shape)<button onClick={() => actor.send({ type: "auth.login", username })}>Login</button>Shared business logic comes from the common demo machine/catalog module (authMachine and catalog), so routing decisions remain machine-driven and framework-independent.
Key Files
src/main.tsx- React entry point that mounts<App />src/App.tsx- actor lifecycle, route-map creation, provider wiring, andShellrenderingsrc/components/- UI components that send actor events and render state-driven viewstest/actor-authority.test.ts- actor authority and guarded navigation behaviortest/strict-separation.test.ts- machine/view infrastructure separation contractstest/browser-e2e/auth-flow.browser.test.tsx- canonical browser auth flowtest/browser/- extended browser checks for URL sync, history behavior, and route event flow
State Machine & Architecture Details
The demo utilizes XMachines architectural invariants:
- Actor Authority: When a user navigates to a protected route, TanStack Router updates the URL. The
PlayRouterProviderintercepts this, translates it to aplay.routeevent, and sends it to the actor. The actor evaluates guards (e.g.isAuthenticated) and executes state transitions. - Passive Infrastructure: The router does not execute route loaders or guards for business logic. The actor dictates whether navigation is permitted.
- Signal-Only Reactivity: The bridge leverages React’s internal mechanisms combined with TanStack’s
router.subscribeto react precisely when signals update, without relying onuseEffectsynchronization inside the application codebase.
Watcher Lifecycle and Cleanup Contract
This demo follows the canonical watcher lifecycle:
notifyqueueMicrotaskgetPending()- Read actor signals and trigger React state updates
- 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
npm run dev # Start Vite dev server (http://localhost:3000)npm run build # Build production assetsnpm run preview # Preview production build locallynpm run test # Run Vitest suitenpm run test:vitest # Explicit Vitest command aliasnpm run test:browser # Browser-focused test runnpm run test:browser:e2e # E2E-specific browser suitenpm run test:e2e # Alias to test:browserVerification
Run the core invariant checks referenced by this demo docs:
npm run test:vitest -- test/actor-authority.test.ts test/strict-separation.test.tsnpm run test:browserManual sanity flow:
- Run
npm run devand openhttp://localhost:3000. - Attempt protected navigation while logged out and confirm guard-driven behavior.
- Log in, then confirm route and view updates stay in sync.