Live demo: routing with a switch statement
Home
Nothing here is a <Route>. One route atom holds which page the URL names, and a plain switch on that value picks the component to render.
SwitchRoutingApp.tsx
import { atom, useAtomValue, useSetAtom } from "jotai";
import { rootRouteAtom, paramRouteAtom, transformRouteAtom } from "jarl-atoms";
import {
SwitchRoutingPage,
isSwitchRoutingPage,
switchRoutingPageLabels,
switchRoutingPages,
} from "./switchRoutingPages";
// The page this demo is mounted on, so everything below it is a plain module-level atom.
const switchRoutingRoot = rootRouteAtom({ basePath: "/demos/switch-routing" });
// One segment out of a fixed set: a paramRouteAtom narrowed by a transformRouteAtom, which also
// types the value as the union the switch below needs.
const pageRoute = transformRouteAtom<{ page: string }, { page: SwitchRoutingPage }>(
paramRouteAtom("page", { parent: switchRoutingRoot }),
({ page }) => (isSwitchRoutingPage(page) ? { page } : undefined),
({ page }) => ({ page }),
);
const currentPageAtom = atom((get): SwitchRoutingPage | "not-found" => {
const page = get(pageRoute);
if (page.match) return page.values.page;
return get(switchRoutingRoot).exact ? "home" : "not-found";
});
const HomePage = () => (
<div>
<h3>Home</h3>
<p>
Nothing here is a <code><Route></code>. One route atom holds which page the URL names, and a plain{" "}
<code>switch</code> on that value picks the component to render.
</p>
</div>
);
const AboutPage = () => (
<div>
<h3>About</h3>
<p>
The buttons above navigated here by writing to that same atom — <code>{`navigate({ page: "about" })`}</code>{" "}
— rather than by following a <code><Link></code>. Writing it set the URL; reading it back is what
moved the switch.
</p>
</div>
);
const ContactPage = () => (
<div>
<h3>Contact</h3>
<p>
Back and forward need nothing extra: the atom derives its value from the location, so the browser's history
moves the switch exactly the way a button does.
</p>
</div>
);
const NotFoundPage = () => (
<div>
<h3>No such page</h3>
<p>
The page route matches only the segments named above, so any other URL under this demo leaves it unmatched and the
switch falls through to this case.
</p>
</div>
);
const pageView = (page: SwitchRoutingPage | "not-found") => {
switch (page) {
case "home":
return <HomePage />;
case "about":
return <AboutPage />;
case "contact":
return <ContactPage />;
case "not-found":
return <NotFoundPage />;
}
};
const SwitchRoutingNav = ({
current,
onNavigate,
}: {
current: SwitchRoutingPage | "not-found";
onNavigate: (page: SwitchRoutingPage) => void;
}) => (
<nav>
{switchRoutingPages.map((page) => (
<button key={page} type="button" disabled={page === current} onClick={() => onNavigate(page)}>
{switchRoutingPageLabels[page]}
</button>
))}
</nav>
);
/**
* Self-contained demo of the least routing machinery that still routes: a `switch` on one route
* atom's value picks the page, and navigation writes to that atom directly.
*/
export const SwitchRoutingApp = () => {
const page = useAtomValue(currentPageAtom);
const navigate = useSetAtom(pageRoute);
return (
<>
<SwitchRoutingNav current={page} onNavigate={(next) => navigate({ page: next })} />
{pageView(page)}
</>
);
};
export default SwitchRoutingApp;