tcmts.react

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

Types

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

Browser

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.
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.
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.
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.
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.
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.

Shared

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.
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.
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.
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.

Files

README file