Skip to content

@xmachines/play-tanstack-solid-router

Documentation / @xmachines/play-tanstack-solid-router

TanStack Solid Router adapter for XMachines Universal Player Architecture

Signals-native integration with TanStack Solid Router enabling logic-driven navigation through Solid.js reactivity.

Overview

@xmachines/play-tanstack-solid-router provides seamless integration between TanStack Solid Router and XMachines state machines. Built on Solid’s reactive primitives, it implements the RouterBridgeBase pattern for bidirectional synchronization while remaining framework-swappable.

Per RFC Play v1, this package implements:

  • Actor Authority (INV-01): State machine controls navigation, router reflects decisions
  • Passive Infrastructure (INV-04): Router observes actor.currentRoute signal
  • Signal-Only Reactivity (INV-05): createEffect synchronizes URL with actor state

Key Benefits:

  • Signals-native: Zero adaptation layer between Solid signals and TC39 Signals
  • Bridge-first: Extends shared RouterBridgeBase policy used by other adapters
  • Automatic tracking: createEffect handles dependency tracking (no manual Watcher setup needed for the bridge)
  • Logic-driven navigation: Business logic in state machines, not components
  • Type-safe parameters: Route params flow through state machine context

Framework Compatibility:

  • TanStack Solid Router 1.100.0+
  • SolidJS 1.8.0+
  • TC39 Signals polyfill integration

Installation

Terminal window
npm install @tanstack/solid-router@^1.108.0 solid-js@^1.8.0 @xmachines/play-tanstack-solid-router @xmachines/play-solid

Peer dependencies:

  • @tanstack/solid-router ^1.108.0 - TanStack Solid Router library
  • solid-js ^1.8.0 - SolidJS runtime
  • @xmachines/play-solid - Solid renderer (PlayRenderer)
  • @xmachines/play-actor - Actor base
  • @xmachines/play-router - Route extraction
  • @xmachines/play-signals - TC39 Signals polyfill

Quick Start

import { Router, createRouter } from "@tanstack/solid-router";
import { PlayTanStackRouterProvider, createRouteMap } from "@xmachines/play-tanstack-solid-router";
import { PlayRenderer } from "@xmachines/play-solid";
import { definePlayer } from "@xmachines/play-xstate";
import { routeTree as routerRouteTree } from "./routeTree.gen"; // from TanStack
function App() {
// 1. Create player with state machine
const createPlayer = definePlayer({
machine: authMachine,
catalog: componentCatalog,
});
const actor = createPlayer();
actor.start();
// 2. Create TanStack router instance
const router = createRouter({ routeTree: routerRouteTree });
// 3. Create route mapping from machine routes
const routeMap = createRouteMap(authMachine);
return (
// 4. Wrap with provider to sync actor and router
<PlayTanStackRouterProvider
actor={actor}
router={router}
routeMap={routeMap}
renderer={(currentActor, currentRouter) => (
<Router router={currentRouter}>
<PlayRenderer actor={currentActor} components={components} />
</Router>
)}
/>
);
}

API Reference

SolidRouterBridge

Router adapter implementing the RouterBridge protocol for TanStack Solid Router.

Type Signature:

class SolidRouterBridge {
constructor(router: Router, actor: AbstractActor<any>, routeMap: RouteMap);
dispose(): void;
}

Constructor Parameters:

  • router - TanStack Solid Router instance
  • actor - XMachines actor instance
  • routeMap - Bidirectional state ID ↔ path mapping

Methods:

  • connect() - Start bidirectional synchronization.
  • disconnect() - Stop synchronization and cleanup bridge resources.
  • dispose() - Alias of disconnect().

Internal Behavior:

  • Uses RouterBridgeBase TC39 watcher lifecycle for actor→router synchronization
  • Updates TanStack Router via router.navigate({ to: path }) when actor state changes
  • Uses router.subscribe to watch history navigation events
  • Sends play.route events to actor when user navigates

PlayTanStackRouterProvider

A Solid component that automatically sets up, connects, and tears down the SolidRouterBridge using Solid’s lifecycle.

interface PlayTanStackRouterProviderProps {
actor: AbstractActor<any>;
router: Router;
routeMap: RouteMap;
renderer: (actor: AbstractActor<any>, router: Router) => JSX.Element;
}

Props:

  • actor - The XMachines player actor
  • router - The TanStack Solid Router instance
  • routeMap - Mapping between paths and state IDs
  • renderer - A render prop function that receives the active actor and router

Behavior:

  1. Instantiates SolidRouterBridge on mount
  2. Calls bridge.connect()
  3. Renders the content returned by the renderer function
  4. Calls bridge.disconnect() when the component unmounts via onCleanup

createRouteMap()

Helper to build a RouteMap instance directly from an XState machine.

Signature:

function createRouteMap(machine: AnyStateMachine): RouteMap;

Usage:

import { createRouteMap } from "@xmachines/play-tanstack-solid-router";
const routeMap = createRouteMap(machine);

Usage Patterns

Dynamic Routes with Parameters

TanStack Router and URLPattern (used internally) support dynamic route matching syntax:

// Machine configuration
const machineConfig = {
states: {
profile: {
meta: {
route: "/profile/:userId",
view: { component: "Profile" },
},
},
},
};
// Route mapping will natively support URLPattern parameters
routeMap.getStateIdByPath("/profile/123"); // → '#profile'

Protected Routes and Guards

With XMachines, auth guards are handled entirely inside the state machine, preventing flashes of unauthorized content.

// Machine side
dashboard: {
meta: { route: "/dashboard", view: { component: "Dashboard" } },
always: {
guard: ({ context }) => !context.isAuthenticated,
target: "login"
}
}

When a user navigates to /dashboard:

  1. TanStack Router updates location
  2. Bridge intercepts and sends play.route
  3. Actor evaluates guard -> denies target, transitions to login instead
  4. Bridge observes new actor state (/login)
  5. Bridge tells TanStack Router to redirect to /login

Circular Update Prevention

The RouterBridgeBase architecture prevents infinite loops between router and actor using two mechanisms:

  1. lastSyncedPath tracking: Stores the last synchronized path to prevent redundant navigations back to the identical location.
  2. isProcessingNavigation flag: Set during a router event, preventing the router from immediately reacting to the actor’s synchronous state update.

Comparison with @solidjs/router Adapter

Aspect@solidjs/router Adapter@tanstack/solid-router Adapter
Router APInavigate(path)router.navigate({ to })
Setup ContextMust be inside Router contextCan wrap Router instance
State SourceUses Solid hooks (useLocation)Subscribes to router directly
ReactingcreateEffect on locationrouter.subscribe callback

Architecture

This package implements standard Play invariants:

INV-01: Actor Authority

State machine has final authority over all transitions. TanStack Router navigation triggers actor events (play.route), but the actor’s guards and transitions ultimately dictate if the view changes.

INV-02: Passive Infrastructure

Infrastructure reflects actor state. The router observes actor.currentRoute and updates the browser URL, never storing independent business state.

Cleanup Contract

The bridge implements an explicit dispose()/disconnect() method. PlayTanStackRouterProvider wires this automatically to Solid’s onCleanup hook to prevent memory leaks and duplicate bridge subscriptions in hot-reloading scenarios.

URLPattern Support

This package uses the URLPattern API for robust route pattern matching via @xmachines/play-router.

URLPattern is available natively on Node.js 24+ and modern browsers (Chrome 95+, Firefox 117+, Safari 16.4+). On older environments, load a polyfill before importing this package — see @xmachines/play-router installation for details.

License

Copyright (c) 2016 Mikael Karon. All rights reserved.

This work is licensed under the terms of the MIT license.
For a copy, see https://opensource.org/licenses/MIT.

Classes

Interfaces

Type Aliases

Functions