Recipes
Common flows that compose more than one feature.
Auth-gated routes
import { navigateTo, NAVIGATING } from "@mongez/react-router";
function authMiddleware({ route, params, localeCode }) { if (!user.isLoggedIn()) { navigateTo("/login"); return NAVIGATING; } return null;}
router.group({ path: "/account", middleware: [authMiddleware], layout: AccountLayout, routes: [ { path: "/", component: AccountDashboard }, { path: "/profile", component: EditProfile }, { path: "/orders", component: Orders }, ],});The NAVIGATING return value tells the wrapper “I redirected, don’t render the page”. The navigateTo call updates the URL and triggers the new render cycle.
Authentication redirect with return-to
import { currentRoute, navigateTo, NAVIGATING } from "@mongez/react-router";
function authMiddleware({ route }) { if (!user.isLoggedIn()) { const returnTo = encodeURIComponent(currentRoute()); navigateTo(`/login?returnTo=${returnTo}`); return NAVIGATING; } return null;}
// After successful login:function onLogin() { const returnTo = queryString.get("returnTo", "/"); navigateTo(decodeURIComponent(returnTo));}Language switcher
import { changeLocaleCode, Link } from "@mongez/react-router";
function LanguageSwitcher() { return ( <select onChange={(e) => changeLocaleCode(e.target.value)}> <option value="en">English</option> <option value="fr">Français</option> <option value="es">Español</option> </select> );}
// Or as links:<Link to="/about" localeCode="en">EN</Link><Link to="/about" localeCode="fr">FR</Link><Link to="/about" localeCode="es">ES</Link>Page analytics on every route change
import { routerEvents } from "@mongez/react-router";
routerEvents.onPageRendered((route, mode) => { // Ignore back/forward navigations if you want if (mode === "swinging") return; analytics.pageview({ path: route, mode });});mode is one of "navigation" | "changeLocaleCode" | "swinging" | "refresh". Scope side effects accordingly.
Scroll restoration on back/forward only
import { routerEvents } from "@mongez/react-router";
let scrollHistory: Record<string, number> = {};
routerEvents.onNavigating((route, mode, previousRoute) => { scrollHistory[previousRoute] = window.scrollY;});
routerEvents.onPageRendered((route, mode) => { if (mode === "swinging") { window.scrollTo(0, scrollHistory[route] ?? 0); } else { window.scrollTo(0, 0); }});The built-in scrollToTop option always scrolls to top on navigating. Disable it (scrollToTop: false) when you want custom logic like this.
Loading bar between navigating and page-rendered
import { routerEvents } from "@mongez/react-router";
let timer: ReturnType<typeof setTimeout> | null = null;
routerEvents.onNavigating(() => { timer = setTimeout(() => loadingBar.start(), 300);});
routerEvents.onPageRendered(() => { if (timer) { clearTimeout(timer); timer = null; } loadingBar.stop();});The 300ms debounce avoids flashing the bar for fast same-bundle transitions.
URL-driven filters
import { queryString } from "@mongez/react-router";
function ProductsList() { const { sort = "name", page = 1 } = queryString.all() as { sort?: string; page?: number };
return ( <> <button onClick={() => queryString.update({ sort: "price", page: 1 }, true)}> Sort by price </button> <Pager page={page} onChange={(next) => queryString.update({ sort, page: next }, true)} /> <ul>…</ul> </> );}The true second arg to update triggers refresh() so the component re-runs with the new params.
Suspense per route
import HeavyChart from "./HeavyChart"; // a React.lazy(...)
setRouterConfigurations({ suspenseFallback: <Spinner />,});
router.add("/dashboard", () => ( // Wrapped in Suspense by the router; the fallback comes from config. <HeavyChart />));The wrapper already wraps every page in <Suspense> with the configured fallback. You don’t need to add it yourself unless you want a per-section fallback.
Composing with state — react-atom
Pair the router with @mongez/react-atom for route-driven state:
import { fetchingAtom } from "@mongez/react-atom";import { routerEvents } from "@mongez/react-router";
const usersAtom = fetchingAtom<User[]>("users");
routerEvents.onPageRendered(async (route) => { if (route !== "/users") return; usersAtom.startLoading(); try { usersAtom.success(await api.users.list()); } catch (err) { usersAtom.failed(err); }});Now usersAtom.useData() / useLoading() work inside any component, and the data is invalidated/refetched declaratively whenever the user navigates onto /users.