createRouter
Creates a reactive router for client-side navigation with full TypeScript support.
const router = createRouter(routes, options?)Basic Usage
import { useRouter } from "rask-ui";
const routes = {
home: "/",
about: "/about",
user: "/users/:id",
post: "/posts/:id?showComments",
} as const;
function App() {
const router = useRouter(routes);
return () => {
if (router.route?.name === "home") {
return <Home />;
}
if (router.route?.name === "user") {
return <User id={router.route.params.id} />;
}
if (router.route?.name === "post") {
return (
<Post
id={router.route.params.id}
showComments={router.route.queries.showComments === "true"}
/>
);
}
return <NotFound />;
};
}Parameters
config
Route configuration object mapping route names to path patterns.
const routes = {
home: "/",
users: "/users",
user: "/users/:id",
userPosts: "/users/:userId/posts/:postId",
} as const;Use :param syntax for dynamic segments. Parameters are automatically extracted and type-checked.
options
Optional configuration object:
base?: string- Base path for all routes (e.g.,/app)
const router = useRouter(routes, { base: "/app" });Properties
route
Current active route with name and params properties.
if (router.route?.name === "user") {
const userId = router.route.params.id; // Type-safe!
}queries
Current URL query parameters as an object.
const searchTerm = router.queries.q || "";
const page = parseInt(router.queries.page || "1");Methods
push(name, paramsQueries?)
Navigate to a route by name, adding a new history entry.
// Navigate to home
router.push("home");
// Navigate with params
router.push("user", { id: "123" });
// Navigate with query
router.push("user", { id: "123", tab: "recent" });replace(name, paramsQueries?)
Navigate to a route without adding a history entry.
router.replace("home");
router.replace("user", { id: "456" });setQuery(query)
Update query parameters without changing the route.
router.setQuery({ page: "2", filter: "active" });Setting queries does not cause rerender. This is a one way sync from your app to the url. Only pushing a new route with updated queries would cause a rerender.url(name, paramsQueries?)
Generate a URL string for a route without navigating.
const userUrl = router.url("user", { id: "123" });
// "/users/123"
const searchUrl = router.url("user", { id: "123", q: "search" });
// "/users/123?q=search"Features
Reactive Integration
The router integrates with RASK's reactivity system. Route changes automatically trigger re-renders.
function UserProfile() {
const router = useRouter(routes);
const state = useState({ user: null });
// Effect runs when route changes
useEffect(() => {
if (router.route?.name === "user") {
fetch(`/api/users/${router.route.params.id}`)
.then((r) => r.json())
.then((data) => (state.user = data));
}
});
return () => <div>{state.user && <h1>{state.user.name}</h1>}</div>;
}Type Safety
Routes are fully type-safe with TypeScript inference.
const routes = {
user: "/users/:id",
post: "/posts/:postId/:commentId",
} as const;
const router = Router(routes);
// ✅ Type-safe
router.push("user", { id: "123" });
// ❌ Type error - missing required params
router.push("post", { postId: "1" });
// ✅ Type-safe params access
if (router.route?.name === "post") {
const postId = router.route.params.postId; // string
const commentId = router.route.params.commentId; // string
}Query Parameters
Built-in support for query string management.
function SearchPage() {
const router = useRouter(routes);
return () => (
<div>
<p>Search: {router.queries.q || "none"}</p>
<input
value={router.queries.q || ""}
onInput={(e) => router.setQuery({ q: e.target.value })}
/>
<p>Page: {router.queries.page || "1"}</p>
</div>
);
}Context Pattern
Share router across components using context.
import { useRouter, createContext } from "rask-ui";
const routes = {
home: "/",
about: "/about",
user: "/users/:id",
} as const;
const RouterContext = createContext<Router<typeof routes>>();
function App() {
const router = useRouter(routes);
const inject = useInjectContext(RouterContext);
inject(router);
return () => <Content />;
}
function Navigation() {
const router = useContext(RouterContext);
return () => (
<nav>
<button onClick={() => router.push("home")}>Home</button>
<button onClick={() => router.push("about")}>About</button>
<button onClick={() => router.push("user", { id: "123" })}>
User 123
</button>
</nav>
);
}Advanced Patterns
Nested Routes
Handle nested routes using path patterns:
const routes = {
settings: "/settings",
settingsProfile: "/settings/profile",
settingsNotifications: "/settings/notifications",
} as const;
function Settings() {
const router = useRouter(routes);
return () => (
<div>
<nav>
<button onClick={() => router.push("settingsProfile")}>Profile</button>
<button onClick={() => router.push("settingsNotifications")}>
Notifications
</button>
</nav>
{router.route?.name === "settingsProfile" && <ProfileSettings />}
{router.route?.name === "settingsNotifications" && (
<NotificationSettings />
)}
</div>
);
}Route Guards
Implement route guards using effects:
function ProtectedApp() {
const router = useRouter(routes);
const auth = useState({ isAuthenticated: false });
useEffect(() => {
// Redirect to login if not authenticated
if (router.route?.name !== "login" && !auth.isAuthenticated) {
router.replace("login");
}
});
return () => <div>{/* App content */}</div>;
}Data Loading
Load data based on route parameters:
function Posts() {
const router = useRouter(routes);
const [postsState, fetchPosts] = useAsync((page: string, signal) =>
fetch(`/api/posts?page=${page}`, { signal }).then((r) => r.json())
);
useEffect(() => {
const page = router.queries.page || "1";
fetchPosts(page);
});
return () => (
<div>
{postsState.isPending && <p>Loading...</p>}
{postsState.value?.map((post) => (
<article key={post.id}>{post.title}</article>
))}
<button onClick={() => router.setQuery({ page: "2" })}>Next Page</button>
</div>
);
}Notes
- Built on typed-client-router
- Uses the browser's History API
- Automatically cleaned up when component unmounts
- Route matching is case-sensitive
- Query parameters are always strings
- Reactive properties (
route,queries) automatically track dependencies - Navigation methods (
push,replace) are not reactive
Related
- createContext - Share router across components
- useEffect - React to route changes
- useAsync - Load data based on routes
