jarl-atoms API reference
The core exports of jarl-atoms - the framework-agnostic half of JARL. Everything here is a
plain jotai atom with no React dependency; the React components and hooks
that consume these atoms live in the sibling jarl-react package. See the
v1 History page for how JARL's original RouteMap/RoutingProvider API worked, and
why the atomic model replaced it.
Every route atom - whatever kind - shares the same shape when read: { match, exact, values, reverse, rest? }. match/exact tell you whether (and how completely) it matches the current
location, values gives you the params it (and its ancestors) bound, and reverse(values)
turns a set of param values back into a URL. Writing to a route atom navigates.
The reference below is generated from the doc comments on each export.
Functions and atoms
locationAtom
const locationAtom: WritableAtom<JarlLocation, [SetStateAction<JarlLocation>, ({ replace?: boolean; } | undefined)?], void>
The location every route atom reads from, and the seam where SSR/SSG is made possible.
In a browser this is exactly atomWithLocation(): reads and writes go
straight through to jotai-location, so navigation still drives real
history.pushState/replaceState and responds to popstate.
Under Node there is no window to push history onto, so writes are captured
in plain jotai state instead and reads prefer that captured value. That makes
a route seedable per-render on the server:
const store = createStore();
store.set(locationAtom, { pathname: "/docs", searchParams: new URLSearchParams() });
renderToString(<Provider store={store}><App /></Provider>);
Each store keeps its own override, so prerendering many routes in one process can't leak location between them.
routeAtom
const routeAtom: <T extends DefaultParams = DefaultParams, Parent extends DefaultParams = DefaultParams>(matchPath: (path: string, get: Getter) => T | undefined, makePath: (values: T, get: Getter) => string, options?: RouteOptions<Parent>) => RouteAtom<T & Parent>
The primitive every other route atom is built from. matchPath decides whether the next
unconsumed path segment matches, and to what param values; makePath is its inverse, used by
reverse. Reach for it directly when staticRouteAtom/paramRouteAtom don't fit - custom
segment syntax, regex constraints and the like.
createRootAtom
const createRootAtom: (options?: RootOptions) => RouteAtom<DefaultParams>
Creates a root RouteAtom. Call this instead of using the default rootAtom export when the
app needs to be scoped under a basePath.
rootAtom
const rootAtom: RouteAtom<DefaultParams>
The default root of every route atom chain: matches /, and is the implicit parent.
staticRouteAtom
const staticRouteAtom: <Parent extends DefaultParams>(name: string, options?: RouteOptions<Parent>) => RouteAtom<Parent>
Matches one fixed path segment: staticRouteAtom("about") matches /about.
paramRouteAtom
const paramRouteAtom: <T extends string, Parent extends DefaultParams>(name: T, options?: RouteOptions<Parent>) => RouteAtom<{ [key in T]: string; } & Parent>
Binds one dynamic path segment to a named value: paramRouteAtom("productId", { parent: products }) matches /products/:productId and yields { productId: "123" }.
transformRouteAtom
const transformRouteAtom: <T extends DefaultParams, Return extends DefaultParams>(parentAtom: RouteAtom<T>, getter: (values: T, get: Getter) => Return | undefined, setter: (values: Return, get: Getter) => T) => RouteAtom<Return>
Reshapes a route's matched values into a different shape, and back again for
reverse/write - composable middleware over a chain of route atoms.
notAtom
const notAtom: (...routes: RouteAtom<any>[]) => Atom<boolean>
Matches when none of the given route atoms are an exact match - the
inverse of a router's full route list, for a catch-all/not-found case.
Checks exact rather than match: an ancestor route (or rootAtom
itself) can be match: true without being the leaf that actually
rendered, and only the leaf's exactness should count.
normalizePathname
const normalizePathname: (pathname: string) => string
Normalizes a pathname to a leading slash, no repeated slashes and no trailing slash.
splitHref
const splitHref: (href: Path) => [pathname: string, searchParams: URLSearchParams]
Splits a full href (e.g. /foo/bar?a=1&b=2) into a normalized pathname
and a URLSearchParams instance for the query string.
appendQueryParam
const appendQueryParam: (href: Path, key: string, value: string | undefined) => Path
Appends/overwrites a single query param onto an existing href, returning the combined
href. Passing undefined as the value removes the param.
joinHref
const joinHref: (pathname: string, searchParams: URLSearchParams) => Path
Joins a pathname and a URLSearchParams back into a single href string.
parseQuery
const parseQuery: (search: URLSearchParams | string) => Record<string, string | string[]>
Parses a URLSearchParams (or query string) into a plain object. Repeated
keys become string arrays, matching the common (non-qs) convention.
stringifyQuery
const stringifyQuery: (query: Record<string, string | string[] | undefined>) => string
Inverse of parseQuery: serializes a plain object into a query string
(without the leading ?).
queryAtom
const queryAtom: WritableAtom<Record<string, string | string[]>, [query: Record<string, string | string[] | undefined>, navOptions?: NavOptions | undefined], void>
Read/write atom for the whole current query string, as a plain object.
Reading never fails to match; writing replaces the entire query string
(pass undefined for a key to remove it, keep other current keys by
spreading get(queryAtom) yourself first).
queryParamAtom
const queryParamAtom: <T extends string, Parent extends DefaultParams = DefaultParams>(name: T, options?: QueryParamOptions<Parent>) => RouteAtom<{ readonly [key in T]: string | undefined; } & Parent>
A single named query param, composable exactly like a path RouteAtom:
it can be given a parent (any RouteAtom, path- or query-based), and its
own reverse()/write round-trip through the same href as its parent, with
this param appended/updated on top. Doesn't consume any path segments, so
path matching continues unaffected by however many query params are
chained on.
redirect
const redirect: (to: Path) => Redirect
Constructs a Redirect sentinel object - typically returned from a resolvedAtom loader to
defer a redirect decision until after data has loaded.
isRedirect
const isRedirect: (value: unknown) => value is Redirect
redirectAtom
const redirectAtom: <Parent extends DefaultParams = DefaultParams>(to: Path | ((get: Getter) => Path), options?: RouteOptions<Parent>) => RouteAtom<Parent>
A leaf route that matches whenever its parent does, swallowing any remaining path, and whose
reverse()/write resolve to the redirect target rather than to itself. to may be a static
path or a function of get, for a target computed from other atoms.
Reading it is pure: matching one navigates nowhere on its own, see followRedirects.
followRedirects
const followRedirects: (store: Store, redirectAtoms: ReadonlyArray<RouteAtom<any>>) => (() => void)
Makes redirect atoms actually navigate: subscribes to each, and replace-navigates the moment one starts matching. Call once, near the root of an app, for every redirect atom you want live. Returns an unsubscribe function.
resolvedAtom
const resolvedAtom: <T extends DefaultParams, Data>(routeAtom: RouteAtom<T>, resolver: Resolver<T, Data>) => Atom<Promise<Data | Redirect | undefined>>
Runs resolver whenever routeAtom matches, resolving to undefined when it doesn't. This
is a plain async atom, so observe it however suits: useAtomValue + Suspense, jotai/utils
loadable() for a non-suspending pending/hasData/hasError view, or await store.get(resolvedAtom) outside React entirely.
followResolvedRedirects
const followResolvedRedirects: (store: Store, resolvedAtoms: ReadonlyArray<Atom<Promise<unknown>>>) => (() => void)
Follows any Redirect a resolver produces, replace-navigating to its target - the
async-loading counterpart of followRedirects. Returns an unsubscribe function.
Classes
Redirect
export class Redirect {
constructor(public readonly to: Path) {}
}
A sentinel object meaning "actually, redirect to this instead".
Types
DefaultParams
export type DefaultParams = {};
The param values a route binds. Empty for routes that bind none, such as a static segment.
NavOptions
export type NavOptions = { replace?: boolean };
Extra argument when writing to a RouteAtom, e.g. set(routeAtom, values, { replace: true }).
replace navigates with history.replaceState rather than history.pushState.
ExtractRouteOptionalParam
export type ExtractRouteOptionalParam<PathType extends Path> = PathType extends `${infer Param}?`
? { readonly [k in Param]: string | undefined }
: PathType extends `${infer Param}*`
? { readonly [k in Param]: string | undefined }
: PathType extends `${infer Param}+`
? { readonly [k in Param]: string }
: { readonly [k in PathType]: string };
The param name a single pattern segment binds, honouring its ?, * and + suffixes.
ExtractRouteParams
export type ExtractRouteParams<PathType extends string> = string extends PathType
? DefaultParams
: PathType extends `${infer _Start}:${infer ParamWithOptionalRegExp}/${infer Rest}`
? ParamWithOptionalRegExp extends `${infer Param}(${infer _RegExp})`
? ExtractRouteOptionalParam<Param> & ExtractRouteParams<Rest>
: ExtractRouteOptionalParam<ParamWithOptionalRegExp> & ExtractRouteParams<Rest>
: PathType extends `${infer _Start}:${infer ParamWithOptionalRegExp}`
? ParamWithOptionalRegExp extends `${infer Param}(${infer _RegExp})`
? ExtractRouteOptionalParam<Param>
: ExtractRouteOptionalParam<ParamWithOptionalRegExp>
: {};
The full param object a :name-style path pattern binds.
JarlLocation
export type JarlLocation = {
pathname?: string;
searchParams?: URLSearchParams;
hash?: string;
};
The location every route atom reads: pathname, query params and hash.
RouteReturn
export type RouteReturn<T extends DefaultParams = DefaultParams> = {
reverse: (values: T) => string;
} & (
| {
match: true;
values: T;
exact: boolean;
rest: { path: string[] };
}
| {
match: false;
exact: false;
values: undefined;
}
);
What reading any route atom gives you. match/exact say whether and how completely it
matches the current location, values holds the params it and its ancestors bound, rest
the path segments left for its children, and reverse turns param values back into a URL.
RouteAtom
export type RouteAtom<T extends DefaultParams> = WritableAtom<RouteReturn<T>, [T, NavOptions?], void>;
A route: read it for its RouteReturn match state, write param values to it to navigate.
RouteOptions
export type RouteOptions<Parent extends DefaultParams> = {
/** Route this one nests under, matching the segment after its parent's. Defaults to `rootAtom`. */
parent?: RouteAtom<Parent>;
};
Common options for every route atom constructor.
RootOptions
export type RootOptions = {
/**
* Scopes the router to a subtree of the URL: the prefix is stripped from the pathname before
* matching begins, and prepended again by `reverse`/write. A location outside `basePath` makes
* the whole tree report `match: false`.
*/
basePath?: Path;
};
Options for createRootAtom.
Path
export type Path = string;
A URL path, optionally with a query string attached (/products/12?page=2).
QueryParamOptions
export type QueryParamOptions<Parent extends DefaultParams> = RouteOptions<Parent> & {
/** Makes a missing query param a non-match; by default it just yields `undefined`. */
required?: boolean;
};
RouteOptions plus whether the param must be present for the route to match.
Store
export type Store = ReturnType<typeof createStore>;
A jotai store, as returned by jotai's own createStore().
Resolver
export type Resolver<T extends DefaultParams, Data> = (values: T, get: Getter) => Promise<Data | Redirect>;
Loads the data a matched route needs. Returning a Redirect sends the app elsewhere instead.