Reading route state
There’s no useRoute() hook in @whisq/router — reading the current route is done directly via router.current, a ReadonlySignal<RouteState>. Every other router primitive (RouterView, Link’s active-class) is built on subscribing to that signal.
This page covers the reactive-access shape: what RouteState exposes, how to read it inside components, and which read form (.value vs callable getter) to reach for in which context.
RouteState
Section titled “RouteState”interface RouteState { path: string; params: Record<string, string>; query: Record<string, string>; matched: MatchedRoute[]; meta: Record<string, unknown>;}
interface MatchedRoute { route: RouteConfig; params: Record<string, string>;}| Field | Type | Description |
|---|---|---|
path | string | Normalised pathname — leading slash, no trailing slash (except "/"). |
params | Record<string, string> | All :name captures across the matched chain, flattened. Nested routes contribute their own params plus ancestors’. |
query | Record<string, string> | Parsed query string. Single values only — repeated keys are overwritten. Empty object when no query. |
matched | MatchedRoute[] | The full chain of matched routes, top-down. Length = nesting depth. Useful for breadcrumbs. |
meta | Record<string, unknown> | Merged meta from every matched route, in matched order (child wins on key collision). Useful for per-route flags like { requiresAuth: true }. |
Reading inside a component
Section titled “Reading inside a component”Use the callable getter form (() => router.current.value.<field>) anywhere Whisq expects a reactive value — text children, reactive props, inside when() / match():
import { component, div, h1, p, span } from "@whisq/core";import { router } from "../router";
export const UserDetail = component(() => div( h1(() => `User ${router.current.value.params.id}`), p(() => `Path: ${router.current.value.path}`), // query: ?tab=settings → "settings" p(() => `Tab: ${router.current.value.query.tab ?? "profile"}`), ),);The render callback’s params argument is a Record<string, string> aggregated from all matched routes up to and including this view’s depth — sugar so shallow components don’t need to import router:
// Reads the params passed by RouterView for this depth.export const UserDetail = ({ id }: Record<string, string>) => div(h1(`User ${id}`));This is a plain object snapshot captured when the component mounts. It’s equal to router.current.value.params when the component sits at the deepest matched level, but a shallower view’s render sees a proper subset (its own params plus ancestors’, not descendants’).
RouterView re-mounts the component on every route change, so the snapshot is always for the current match — no stale captures across navigations. But inside an already-mounted component, if you need a value that reads reactively without a re-mount (uncommon — happens mostly with hash-only changes or programmatic rewrites), reach for the signal:
// Always reads the current value — works even if the framework// short-circuits a re-mount for an identical route swap.h1(() => `User ${router.current.value.params.id}`)Reading inside an effect
Section titled “Reading inside an effect”router.current.value establishes a subscription, so the effect re-runs on every route change:
import { effect } from "@whisq/core";
effect(() => { const route = router.current.value; document.title = route.meta.title as string | undefined ?? "App";});If you only care about a specific field, read just that to keep the effect’s dependency set tight:
effect(() => { const tab = router.current.value.query.tab ?? "profile"; loadTab(tab);});The effect still re-runs on every navigation (since router.current is a single signal), but reading the narrow projection up front makes the intent obvious and keeps the effect body focused.
peek() — read without subscribing
Section titled “peek() — read without subscribing”For one-shot reads in non-reactive contexts (event handlers, analytics calls):
button({ onclick: () => { const current = router.current.peek().path; analytics.track("logout", { from: current }); },}, "Log out").peek() reads the current value without entering the tracking scope — the effect or component around this button won’t re-render on route change just because it read the path.
Patterns
Section titled “Patterns”- Breadcrumbs — iterate
router.current.value.matched, readingmeta.title(or another per-route field) on eachMatchedRoute. - Per-route page title —
effect(() => document.title = router.current.value.meta.title ?? "App")as shown above. Prefer this overuseHeadinside per-route components when the title logic lives outside the routes. - Query-string-driven state — treat
router.current.value.query.xas the source of truth for a query-gated UI mode; write back viarouter.navigatewith a rebuilt query string.
See also
Section titled “See also”/api/createrouter/— creates theRouterthat ownscurrent./api/routerview/— renders the component matched bycurrent./guides/routing/#accessing-route-state— the routing guide’s take on this pattern.
Docs current to v0.1.0-alpha.9 . All releases →