mobx-route
v2.1.2
Published
Simple and lightweight typed router
Maintainers
Readme
mobx-route
🚀 Simple and lightweight typed MobX router 🚀
Uses path-to-regexp power for path matching
📖 Read the docs →
Quick Start
import { createRoute } from "mobx-route";
const userDetails = createRoute("/users/:id");
// Path params are required — TypeScript enforces it
await userDetails.open({ id: 1 });
userDetails.isOpened; // true
userDetails.params; // { id: "1" } — fully typed✨ Features
🔗 Nested Routes with .extend()
Build route trees naturally — no config arrays, no <Routes> wrappers:
const users = createRoute("/users");
const userDetails = users.extend("/:userId");
const userPhotos = userDetails.extend("/photos");
// Path is auto-concatenated: /users/:userId/photos
await userPhotos.open({ userId: 42 });
// → /users/42/photos
users.isOpened; // true (parent is open too)
users.hasOpenedChildren; // true🛡️ Route Guards & Redirects
Protect routes with beforeOpen — cancel navigation or redirect:
const dashboard = createRoute("/dashboard", {
beforeOpen: async () => {
if (!await isAuthenticated()) {
return { url: "/login", replace: true }; // redirect
}
// return undefined → proceed
},
checkOpened: () => currentUser.isAuthorized, // reactive predicate
});🔮 Virtual Routes for Modals & Drawers
Same .open() / .close() / .isOpened API — but no URL involved:
const authModal = createVirtualRoute({
checkOpened: (route) => route.query.data.modal === "auth",
open: (_, route) => route.query.update({ modal: "auth" }),
close: (route) => route.query.update({ modal: undefined }),
beforeClose: () => !hasUnsavedChanges, // prevent closing
});
authModal.isOpened; // reactive — auto-updates from query
authModal.isClosing; // for exit animations🎯 Typed Query Params
const search = createRoute<
"/search",
{},
{},
{ q: string; page?: number; sort?: "asc" | "desc" }
>("/search");
// TQueryParams types the INPUT — what you pass to open()
await search.open({}, { query: { q: "mobx", page: 1 } });
// query.data is always Record<string, string> at runtime (values come from URL)
search.query.data.q; // string
search.query.data.page; // string | undefined — use Number() or QueryParam for typed access🔄 update() for In-Place Changes
Replace params without polluting browser history:
await userRoute.open({ userId: 1 }, { query: { tab: "profile" } });
await userRoute.update({ userId: 2 });
// → /users/2?tab=profile (replace: true, mergeQuery: true by default)🧩 React Integration
import { RouteView, RouteViewGroup, Link } from "mobx-route/react";
// Declarative route rendering
<RouteView route={userRoute} view={UserPage} fallback={<Loading />} />
// Route switching with fallback
<RouteViewGroup otherwise={notFoundRoute}>
<RouteView route={homeRoute} view={HomePage} />
<RouteView route={userRoute} view={UserPage} />
<div>Not found</div>
</RouteViewGroup>
// Type-safe links
<Link to={userRoute} params={{ userId: 42 }}>Profile</Link>🧠 View Model Integration
import { RouteViewModel } from "mobx-route/view-model";
class UserPageVM extends RouteViewModel<typeof userRoute> {
route = userRoute;
// payload, pathParams, query, isMounted — all built-in
}🌍 Optional Path Segments & Wildcards
// Optional segment
const route = createRoute("/users{/:tab}");
route.open(); // → /users
route.open({ tab: 1 }); // → /users/1
// Wildcard/rest params
const docs = createRoute("/docs/*rest");
docs.open({ rest: ["api", "v2", "auth"] }); // → /docs/api/v2/auth📦 Tree-Shakeable Subpath Exports
Only pay for what you use:
import { createRoute } from "mobx-route"; // core only
import { RouteView, Link } from "mobx-route/react"; // + React
import { RouteViewModel } from "mobx-route/view-model"; // + VMInstallation
npm install mobx-route
# or
pnpm add mobx-route
# or
yarn add mobx-routePeer dependencies (React integration is optional):
npm install mobx
# For React:
npm install mobx-react-lite react react-domContribution Guide
Want to contribute? Follow this guide
