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.