Skip to content

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.