Path Variables
Now we've seen how to set up some static routes, let's look at something a bit more advanced. Routing isn't much use if we have to define every single URL statically!
paramRouteAtom binds a single dynamic path segment to a named value, composed on top of a
parent route atom (any route atom - rootRoute, a staticRouteAtom, or another
paramRouteAtom, for nested dynamic segments):
routes.ts:
import { staticRouteAtom, paramRouteAtom } from "jarl-atoms";
export const productsRoute = staticRouteAtom("products");
export const productRoute = paramRouteAtom("productId", { parent: productsRoute });This matches /products/:productId-shaped URLs: productsRoute consumes the products
segment, and productRoute binds whatever segment follows it to productId. Reading
productRoute (via useRoute, or the values passed to <Route>'s render-prop children)
gives you { productId: "123" } once it matches:
import { useEffect, useState } from "react";
import { Route } from "jarl-react";
import { productRoute } from "./routes";
const ProductPage = ({ productId }: { productId: string }) => {
const [product, setProduct] = useState<Product | null>(null);
useEffect(() => {
fetch(`/api/products/${productId}`)
.then((result) => result.json())
.then(setProduct);
}, [productId]);
return product ? <ProductView product={product} /> : "Loading...";
};
export default () => (
<Route on={productRoute} exact>
{({ productId }) => <ProductPage productId={productId} />}
</Route>
);In this example we're optimistically loading the page and displaying a "Loading..." spinner while the data loads. See the Data Loading guide for a better way to manage this, by moving the fetch into the route atom itself.
Linking to a dynamic route works the same way as a static one - just pass the param values as
to:
<Link route={productRoute} to={{ productId: "123" }}>
Our Best Product Ever!
</Link>Segments from a fixed set
A dynamic segment usually isn't dynamic in the sense of "anything at all" - it's one of a handful of
known values. enumRouteAtom matches exactly those and nothing else, and types the value it binds
as the union of them rather than as string:
import { staticRouteAtom, paramRouteAtom, enumRouteAtom } from "jarl-atoms";
const productTabs = ["overview", "reviews", "specs"] as const;
export const productsRoute = staticRouteAtom("products");
export const productRoute = paramRouteAtom("productId", { parent: productsRoute });
export const productTabRoute = enumRouteAtom("tab", productTabs, { parent: productRoute });/products/123/reviews matches with { productId: "123", tab: "reviews" }, where tab is
"overview" | "reviews" | "specs" - so a switch over it is exhaustive with no default case to
write, and <Link route={productTabRoute} to={{ productId: "123", tab: "reveiws" }}> is a typo the
compiler catches rather than a dead link. /products/123/pricing doesn't match at all, so it falls
through to whatever the app renders for an unknown URL instead of reaching the tab page with a value
it has nothing to show for.
numericRouteAtom does the same job for a segment that has to be a whole number, optionally within
a min/max range, binding it as a number rather than a string. For a constraint neither one
covers - a checksum, a slug in a database, a rule spanning several segments - see
transformRouteAtom and validateRouteAtom in the API reference.
Query parameters
Dynamic path segments aren't the only way to carry a value in a URL - queryParamRouteAtom (from
jarl-atoms) does the same job for a single named query-string parameter, composed on top of a
parent route atom exactly like paramRouteAtom, except it doesn't consume a path segment:
import { staticRouteAtom, queryParamRouteAtom } from "jarl-atoms";
export const searchRoute = staticRouteAtom("search");
export const searchQueryRoute = queryParamRouteAtom("q", { parent: searchRoute });searchQueryRoute matches whenever /search does, with values.q set to the current ?q=
value (or undefined if it's missing - pass { required: true } to make a missing query
param a non-match instead, if the page needs one to render anything meaningful). Reading,
writing, and linking all work exactly as they do for a path variable - reverse/Link append
or update just that one query param, leaving the path and any other query params untouched.
Need the whole query string as an object, rather than one named param at a time? queryAtom
(also from jarl-atoms) reads/writes it all at once.