react

React's routes, links and data for a tcm app, as a library: tcm ships it, so tcm lib add tcm react adds the release your tcm has, with no network (and tcm lib upgrade react the one a newer tcm has). It's also announced by tcm.codes (tcm lib add @tcm.codes/react), and comes by hash from any tcm server that has it. It's a tcm library, not an npm package: it holds everything its code uses but React itself, so a codebase that adds it has every definition named.

import { Pages, Stylesheet, useAppLinks, useData, useLocation } from "#codebase/app/lib/react";
import { styles } from "#codebase/app";

export const App = () => {
  useAppLinks();
  return (
    <>
      <Stylesheet css={styles} />
      <Pages routes={{ "/": Home, "/n/:id": Note }} />
    </>
  );
};
const Note = ({ params }: { params: { id: string } }) => {
  const note = useData(`note:${params.id}`, () => readNote(params.id), { keep: true });
  return <h1>{note.value?.title}</h1>;
};

What it has

A navigation is a transition (state is React state, never useSyncExternalStore): what's on screen stays until the next page is ready, and <ViewTransition> can animate it (navigate, navigate-forward, navigate-back).

tcm's runtime

It reaches tcm's runtime through the page's globals the runtime sets (__TCM_BASE__, __TCM_STYLESHEETS__, __TCM_TREES__, __tcmPrefetch, __tcmUpdate): version 1 of tcm's host protocol, as its manifest says. A tcm that doesn't serve that version won't build an app using it, rather than build one that breaks.

Its versions follow semver from its API, as any package's do (releases).

test/
4 definitions

Apps

refresh (prefix?: string | undefined) => Promise<void>
Read again what's shown whose key starts with `prefix` (all of it, by default), after a change; what isn't shown is read when it is. Without a prefix, the page's server components are rendered again too, as after a server function call.

Types

Data interface
Something read for a component, by key.
PageProps interface
What a page of `<Pages>` is given: its route's parameters (`/p/:id` → `{ id }`) and the query.
Where interface
Where the page is: its path, query and fragment.

Functions

Pages (props: { routes: Record<string, (props: PageProps) => Promise<ReactNode> | ReactNode>; keep?: numb…
The page the path matches (the first of `routes`, by `match`; `notFound` if none). The last `keep` pages left (3) stay mounted but hidden (React's `<Activity>`), so going back shows one at once; the page a link to is pointed at is rendered ahead, hidden. The server's first paint is the page shown, alone.
Stylesheet ({ css, precedence, }: { css: string; precedence?: string | undefined; }) => ReactNode
CSS for the page, through React's stylesheets: once however many components render it, kept when they're gone, ahead of the page's content, grouped by `precedence` (`"default"`). A stylesheet of the codebase (a `.css` file) is served by the app's build as a file of its own and linked to (`__TCM_STYLESHEETS__`, filled as its modules load): the page waits for it to load rather than show unstyled, and the browser keeps it. Other CSS is written into the page.
createRouter (page: typeof globalThis) => { useLocation: () => Where; useNavigating: () => boolean; navigate: (t…
What a page's routes, links and data keep while it's open, over the page's globals (`globalThis` in the browser; the server's first paint lacks some of them): the location and its watchers, the data read, the pages kept. One of them, router, is what the library's hooks and `<Pages>` use.
href (path: string) => string
A path of the app as a link's address: under the app's base, if it's served under one (`/s/1` → `/examples/notes/s/1`); one under it already (`location.pathname`), and other URLs, as they are. `navigate` takes either.
match (pattern: string, path: string) => Record<string, string> | undefined
A path's parameters if it matches the pattern (`/s/:owner/:key`, `/docs/*`), decoded; undefined if it doesn't.
navigate (to: string, options?: { replace?: boolean | undefined; scroll?: boolean | undefined; } | undefined…
Go to a path of the app without reloading the page (another site's URL is loaded), in a transition typed `navigate` and `navigate-forward` or `navigate-back`: what's on screen stays until the next page is ready. `replace` doesn't add a history entry; `scroll` (the default unless `replace`) scrolls to the top, or to the fragment.
useAppLinks (isApp?: ((path: string) => boolean) | undefined) => void
Follow the page's links to the app (`<a href>` anywhere on it) without reloading the page. Links with a modifier key, a `target`, `download` or `rel="external"` are left to the browser, and so is a path `isApp` says isn't the app's.
useAppUpdate () => { ready: boolean; reload(): void; }
Whether a new version of the app is ready: a PWA's service worker took the new build in (`reload` loads it now). Always false for an app that isn't a PWA, and until the page is hydrated.
useData <T>(key: string | undefined, read: () => Promise<T>, options?: { keep?: boolean | undefined; } | un…
Read something once per key while the page is open: `key` names what `read` reads. What's read already is shown at once; `keep` shows the previous key's while the next is read. An undefined key reads nothing.
useLocation () => Where
Where the page is; the component re-renders when it changes. While the server renders the first paint, the page's URL. Under a base, `path` is within it: `/` is the base.
useNavigating () => boolean
Whether a navigation (`navigate`, back or forward) is still rendering: the next page is on its way, and the last one is still on screen.

Values

manifest { name: string; npm: boolean; host: number; roles: { Stylesheet: string; }; }
React's routes, links and data for a tcm app, as a library: the page's location and navigation, following its links, data read once per key, a PWA's update, pages kept and rendered ahead, and stylesheets linked to. It reaches tcm's runtime through the page's globals (host protocol 1); `Stylesheet` has the `stylesheet` role, so the build links the stylesheet files it's given.
router { useLocation: () => Where; useNavigating: () => boolean; navigate: (to: string, options?: { replac…
The page's routes, links and data: the one router the hooks and `<Pages>` share.

Files

README file