Declarative, predictable routing for React & React Native powered by finite state machines and MobX
Live Demo • Installation • Quick Start • API • URL Persistence • Examples
Traditional routers map URLs to components. MobX State Machine Router takes a different approach:
- 🎯 State Machine First — Define valid states and transitions. Invalid navigation is impossible by design.
- ⚡ MobX Powered — Reactive state updates with fine-grained re-rendering. No unnecessary renders.
- 🔗 URL Persistence Optional — Works great without URLs (React Native) or with hash/browser history.
- 🔒 Type Safe — Full TypeScript support with inferred types for states, actions, and params.
- 🪶 Lightweight — ~3KB gzipped with zero dependencies (MobX is a peer dependency).
# npm
npm install @mobx-state-machine-router/core mobx
# yarn
yarn add @mobx-state-machine-router/core mobx
# pnpm
pnpm add @mobx-state-machine-router/core mobximport MobxStateMachineRouter, { TStates } from '@mobx-state-machine-router/core';
// 1. Define your states and actions as string literal types
type State = 'home' | 'products' | 'product-detail';
type Action = 'go-products' | 'view-product' | 'go-home';
type Params = {
productId?: string;
};
// 2. Define the state machine
const states: TStates<State, Action> = {
home: {
actions: {
'go-products': 'products',
},
},
products: {
actions: {
'go-home': 'home',
'view-product': 'product-detail',
},
},
'product-detail': {
actions: {
'go-home': 'home',
'go-products': 'products',
},
},
};
// 3. Create the router
const router = MobxStateMachineRouter<State, Params, Action>({
states,
currentState: { name: 'home', params: {} },
});
// 4. Navigate by emitting actions
router.emit('go-products');
console.log(router.currentState.name); // 'products'
// Pass params with navigation
router.emit('view-product', { productId: '123' });
console.log(router.currentState.params); // { productId: '123' }import { observer } from 'mobx-react-lite';
import { router } from './router';
const App = observer(() => {
const { name, params } = router.currentState;
return (
<div>
<nav>
<button onClick={() => router.emit('go-home')}>Home</button>
<button onClick={() => router.emit('go-products')}>Products</button>
</nav>
{name === 'home' && <HomePage />}
{name === 'products' && <ProductsPage />}
{name === 'product-detail' && <ProductDetail id={params.productId} />}
</div>
);
});Creates a new router instance.
const router = MobxStateMachineRouter<States, Params, Actions>({
states: TStates<States, Actions>, // State machine definition
currentState?: { // Initial state (optional)
name: States,
params: Params,
},
persistence?: IPersistence, // URL persistence layer (optional)
});Observable object containing the current state name and params.
router.currentState.name // Current state name
router.currentState.params // Current params objectTransition to a new state by emitting an action.
router.emit('go-home'); // Simple navigation
router.emit('view-product', { id: '1' }); // With paramsObserve changes to a specific param.
import { observeParam } from '@mobx-state-machine-router/core';
observeParam(router, 'currentState', 'productId', (change) => {
console.log('productId changed:', change.newValue);
});import { observe, autorun } from 'mobx';
// React to any state change
observe(router, 'currentState', ({ newValue }) => {
console.log('Navigated to:', newValue.name);
});
// Or use autorun for reactive effects
autorun(() => {
analytics.track('page_view', { page: router.currentState.name });
});Guard navigation or redirect users:
import { intercept } from 'mobx';
intercept(router, 'currentState', (change) => {
// Redirect unauthenticated users
if (change.newValue.name === 'admin' && !isLoggedIn) {
return { ...change, newValue: { name: 'login', params: {} } };
}
return change;
});import interceptAsync from 'mobx-async-intercept';
interceptAsync(router, 'currentState', async (change) => {
if (change.newValue.name === 'checkout') {
const canCheckout = await validateCart();
if (!canCheckout) {
return { ...change, newValue: { name: 'cart-error', params: {} } };
}
}
return change;
});Add URL synchronization with hash or browser history:
pnpm add @mobx-state-machine-router/url-persistence historyimport MobxStateMachineRouter from '@mobx-state-machine-router/core';
import URLPersistence from '@mobx-state-machine-router/url-persistence';
// Add URL to each state
const states: TStates<State, Action> = {
home: {
actions: { 'go-products': 'products' },
url: '/',
},
products: {
actions: { 'view-product': 'product-detail' },
url: '/products',
},
'product-detail': {
actions: { 'go-products': 'products' },
url: '/product',
},
};
// Create router with URL persistence
const router = MobxStateMachineRouter<State, Params, Action>({
states,
currentState: { name: 'home', params: {} },
persistence: URLPersistence(), // Uses hash history by default
});
// Navigation now updates the URL!
router.emit('view-product', { productId: '123' });
// URL: /#/product?productId=123import { createBrowserHistory, createMemoryHistory } from 'history';
// Browser history (requires server configuration)
URLPersistence({ history: createBrowserHistory() });
// Memory history (useful for testing)
URLPersistence({ history: createMemoryHistory() });Handle complex param types:
URLPersistence({
serializers: {
filters: {
getter: (value) => JSON.parse(decodeURIComponent(value)),
setter: (value) => encodeURIComponent(JSON.stringify(value)),
},
},
});Live Demo — Try it in your browser!
Check out the example app for a complete demo with:
- Multi-page navigation
- URL persistence with hash routing
- Query parameter filtering
- TypeScript throughout
Run locally:
cd example
pnpm install
pnpm devThis library is written in TypeScript and provides full type inference:
// States and actions are type-checked
router.emit('invalid-action'); // TS Error!
// Params are typed
router.emit('view-product', { productId: 123 }); // TS Error if wrong type| Package | Description |
|---|---|
| @mobx-state-machine-router/core | Core router with state machine logic |
| @mobx-state-machine-router/url-persistence | URL synchronization with hash/browser history |
- MobX 4.x, 5.x, or 6.x
- Node.js 18+
Contributions are welcome! Please read our contributing guidelines before submitting a PR.
# Install dependencies
pnpm install
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Build packages
pnpm build
# Run example app
cd example && pnpm devMIT © Anzor Bashkhaz
