Overview
Router state lives on the MatesUtils scope that renderApp provides. Read and navigate through useUtils() — there are no global pathAtom / navigateTo imports.
Mount at most one Router or animatedRouter per app. Each outlet calls claimRouterOutlet so a second mount throws. Nest screens with conditionals — not nested routers.
navigateTo and URL atom .set / .update are bidirectional: writing an atom navigates, and navigation updates the atoms.
URL atoms at a glance
|
Piece
|
Role
|
Notes
|
|---|---|---|
pathAtom
| pathname |
Pathname only — never includes ? or #.
|
qsAtom / hashAtom
| parsed objects |
Synced by navigateTo; writes default to replace.
|
paramsAtom
| route params |
From pathAtom + patternsAtom — not props.
|
navigateTo
| history + sync | Updates history, then syncs path / qs / hash atoms. |
claimRouterOutlet
| one outlet | Used by Router / animatedRouter — mutual exclusion per scope. |
navigateTo & atom writes
const { navigateTo, pathAtom, qsAtom, hashAtom, paramsAtom } = useUtils();
navigateTo('/about'); // pushState
navigateTo('/login', true); // replaceState
navigateTo('/users/1', false, { from: 'list' }); // history.state
// Bidirectional: atom writes also navigate — second arg is replace
pathAtom.set('/about'); // push (default)
pathAtom.set('/login', true); // replace
qsAtom.set({ tab: 'bio' }); // replace (default for qs/hash/params)
paramsAtom.set({ id: '99' }); // replace — rebuilds path from matched pattern
// Query + hash are first-class — pathAtom stays pathname-only
navigateTo('/users/42?tab=bio#section=1');
pathAtom(); // "/users/42"
qsAtom(); // { tab: "bio" }
hashAtom(); // { section: 1 }
// Omit ? or # to preserve the current query / hash
navigateTo('/users/99'); // keeps existing search + hash
Nested UI — conditionals, not nested routers
Keep one outlet, and nest screens with
paramsAtom / qsAtom / pathAtom.
import { html, nothing, Router, useUtils, x } from 'mates';
// One Router per app. Nested screens: conditionals on params / qs — not another router.
const GalleryPage = () => {
const { paramsAtom, navigateTo } = useUtils();
return () => {
const { photoId } = paramsAtom();
return html`
<h1>Gallery</h1>
<button @click=${() => navigateTo('/gallery/photo/7')}>Open photo 7</button>
${photoId ? x(PhotoOverlay, { id: photoId }) : nothing}
`;
};
};
const App = () => {
const appRouter = Router([
{ path: '/', component: HomePage },
{ path: '/gallery', component: GalleryPage },
{ path: '/gallery/photo/:photoId', component: GalleryPage },
]);
return () => html`${appRouter}`;
};
Tiny Router (two routes)
Real Router + navigateTo in the sandbox iframe (own MatesUtils scope).
Bidirectional atom.set
pathAtom.set and qsAtom.set call navigateTo. Prefer navigateTo when setting path + query together.