@silgi/<library>

The six adapters, @silgi/react, @silgi/preact, @silgi/vue, @silgi/solid, @silgi/svelte and @silgi/html, export the same names. Each name has one section here, and a table shows its form in each library: what it takes and what it returns. The examples are React unless the section says otherwise; Adapters has the same page in every library.

Where a table cell says —, that adapter does not export the name. Types written Accessor<T> are Solid's, Ref<T>, ComputedRef<T> and ShallowRef<T> are Vue's.

Each package also has the entries in Other entries.

Components

An <a> that Silgi turns into a client navigation. to is an app path or a route location { name, params?, query?, hash? }, checked against the route names once silgi typegen ran; a string to is a typed path (LinkTarget<T>), so a literal path the app does not answer, or a scheme app.linkProtocols does not list, fails tsc on that line. LinkProps<T> takes that string as T. For a path the href is withBase(to, app.baseURL), and a target that does not start with a single / (a URL with a scheme, //host, a ?/#-only or a relative target such as ../x) stays as given; a location becomes its URL under app.baseURL, and an unknown name or a missing required param throws as navigate does. replace replaces the history entry. prefetch (boolean, default true) and noPrefetch (same as prefetch={false}) turn the prefetch on or off; prefetchOn picks the trigger: "interaction" (default; hover and focus), "visibility" (in view) or { visibility?, interaction? } for both. The rendered attribute is data-silgi-prefetch, a list of hover and visible or none. The old strings (prefetch="hover") fail tsc; at run time they still work for one release and warn once in dev. state is a serializable object (04 §4, at most 65 536 characters serialized) stored with the history entry the click pushes and read as useRoute().state, as navigate(to, { state }) does; it is kept in memory for the <a> element and never written into the HTML, and a state that does not serialize is logged and the click navigates without it. In html the server writes the <a> with data-silgi-link, the occurrence of its href among the links with a state in the render; the client matches it by href and that number, removes it, and ignores one it does not know. A ref given to Link gets the <a> also when state is set. Other props go to the <a>.

A link to the current page's path, or to a nested parent's (route.matched), is an active link and gets activeClass (default "router-link-active") after the class you gave. A link whose path equals route.path is an exact-active link: it also gets exactActiveClass (default "router-link-exact-active") and aria-current set to ariaCurrentValue (default "page"; one of "page", "step", "location", "date", "time", "true"). The query and the hash never count, a trailing / is ignored, and a target that is external, relative or ?/#-only is neither. The marks are right in the server HTML and follow every client navigation.

ReactPreactVueSolidSveltehtml
formcomponentcomponentcomponentcomponentcomponentfunction returning a string
contentchildrenchildrendefault slotchildrenchildren snippetchildren string
LinkPropsyesyes—yes—yes
import { Link } from "@silgi/react";
import type { LinkProps } from "@silgi/react";

const external: LinkProps = { to: "/pricing", prefetchOn: "visibility", className: "nav" };

export default function Nav() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/blog" replace prefetch={false}>Blog</Link>
      <Link {...external}>Pricing</Link>
    </nav>
  );
}

Form, FormProps

A <form> that Silgi turns into a client submission, as Link does for an anchor. With a handler as action it renders a plain <form method="post" action data-silgi-form>, so the page works with JavaScript off: the browser posts to the page and the document path answers (useAction(fn) and useForm(fn) have the result). With JavaScript on, the form calls the handler through its stub, with no document request. Any other action renders a GET form that navigates with the fields as the query. A POST form to anything else throws TypeError: Form posts through a handler: Form action={fn}; use a plain <form> for another URL.

PropDefaultDoes
actionthe page on screen (route.fullPath without the hash)the action attribute; an app path gets app.baseURL as Link's to does, and a route location { name, params? } is built from the route table; a handler (action={fn}) renders method="post" and action="<the page on screen, with its query>?_fn=<id>" (&_fn= after a query; :<instance> follows the id when instance is set): with JavaScript the stub runs instead; without it the post runs the function and the same response renders the page, with the result in useAction(fn).state, .failure or .error. A GET handler throws TypeError: Form action takes a POST handler; <label> is GET. On html the form is a document post (data-silgi-reload), as a hydrated html page runs no component code
method"get", "post" with a handler"get" for a path or a route location; "post" is the only method with a handler
encTypeurlencoded"application/x-www-form-urlencoded" or "multipart/form-data"
replacefalsereplace the history entry (data-silgi-replace)
resetfalsereset the form after a success (data-silgi-reset)
reloadDocumentfalseleave the form to the browser (data-silgi-reload)
refreshtruefalse leaves the page's data alone after a success (data-silgi-refresh)
instancewith action={fn}: the key of the useAction(fn, { instance }) store this form belongs to, so one handler can sit in a list of forms; written after the id in _fn
restoretruefalse keeps the typed values out of a no-JavaScript failure page (data-silgi-restore)
onSubmit({ formData, submitter, cancel }) => void, before the request; cancel() stops the submission

Other props go to the <form>. The component registers onSubmit with enhance (see silgi/app) after it mounts and removes it when it unmounts.

ReactPreactVueSolidSveltehtml
formcomponentcomponentcomponentcomponentcomponentfunction returning a string
contentchildrenchildrendefault slotchildrenchildren snippetthe second argument, a string
onSubmitproppropprop (@submit)propprop— (call enhance from a page script)
FormPropsyesyes—yes—yes
<Form action={removeTodo} instance={todo.id}>
  <input type="hidden" name="id" value={todo.id} />
  <button>Remove</button>
</Form>

In html: Form({ action: removeTodo }, '<button>Remove</button>').

Outlet

Marks where the inner layout or the page goes. The root component app/app.<ext> renders it too. A layout also receives that content as children (a slot in Vue). See Routing.

ReactPreactVueSolidSveltehtml
form() => ReactNodecomponentcomponent() => JSX.Elementcomponent() => string

ClientOnly

Renders fallback on the server and during hydration, and children after it. The html adapter has no ClientOnly: it has no client render to wait for.

ReactPreactVueSolidSveltehtml
propschildren, fallbackchildren, fallbackdefault slot, fallback slotchildren, fallbackchildren, fallback snippets—
import type { ReactNode } from "react";
import { ClientOnly, Outlet } from "@silgi/react";

export default function Layout({ children }: { children?: ReactNode }) {
  return (
    <div>
      <ClientOnly fallback={<span>Loading clock</span>}>
        <time>{new Date().toLocaleTimeString()}</time>
      </ClientOnly>
      <main>{children}</main>
      <Outlet />
    </div>
  );
}

ErrorBoundary, ErrorBoundaryProps

Catches render errors, in the browser and on the server, and renders the fallback with { error, reset } (ErrorBoundaryFallbackProps). reset renders the children again; on the server it does nothing. The limits per library are in Errors.

ReactPreactVueSolidSveltehtml
formclassclasscomponentfunctioncomponentfunction
fallbackfunction returning ReactNodefunctionfunction or #fallback slotfunctionfallback snippetfunction returning a string
ErrorBoundaryProps typeyesyes———yes (children is a function)
import { DefaultErrorPage, ErrorBoundary, clearError, useError } from "@silgi/react";
import type { ErrorBoundaryProps } from "@silgi/react";

const fallback: ErrorBoundaryProps["fallback"] = ({ error, reset }) => (
  <button type="button" onClick={reset}>{String(error)}</button>
);

export function Banner() {
  const error = useError();
  if (!error) return null;
  return (
    <div>
      <DefaultErrorPage error={error} />
      <button type="button" onClick={() => clearError({ redirect: "/" })}>Home</button>
    </div>
  );
}

export function Safe({ children }: { children: React.ReactNode }) {
  return <ErrorBoundary fallback={fallback}>{children}</ErrorBoundary>;
}

DefaultErrorPage

The error page Silgi uses when the app has no error page: the status, the message and, in dev, the stack. Use it inside your own error page or boundary. It takes { error }, an ErrorPageProps of silgi/app: the status, message and optional stack of the error.

HtmlProps, HtmlChildren

html only. The props of a page or layout function: route (a RouteValue), children (the inner content, typed string | HtmlChildren), error and any prop a parent passed. children renders the inner level when it is turned into text, so a layout that destructures it can still wrap it in ErrorBoundary; use ${children} or String(children) before you compare it or test it for emptiness, since typeof children is "object" and children === "..." is false. HtmlChildren is the exported type of that value: toString(), valueOf() and toJSON() return the text.

import { ErrorBoundary, Link, Outlet, useData, useHead, useNavigation, useRoute, useState } from "@silgi/html";
import type { HtmlProps, NavigationState } from "@silgi/html";

const escape = (value: unknown) => String(value).replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);

const busy = (navigation: NavigationState) => navigation.state === "loading";

export default function Layout({ route, children }: HtmlProps): string {
  const { title } = useData<{ title: string }>();
  const clicks = useState("clicks", 0);
  useHead({ title });
  return `<nav>${Link({ to: "/", children: "Home" })}</nav>
<main data-path="${escape(useRoute().path)}" data-route="${escape(route.name)}" data-busy="${busy(useNavigation())}">
${ErrorBoundary({ children: () => children ?? Outlet(), fallback: ({ error }) => `<p>${escape(error)}</p>` })}
</main>
<p>${escape(clicks.value)} clicks</p>`;
}

Route and navigation

useRoute

The current route: path, fullPath (path with ?query and #hash, without app.baseURL), params, query, hash, name, meta and matched, a RouteMatch[] from silgi/adapter: one { name, path, meta } per page from the outermost to the current one, where path is the pattern and meta that page's own meta export. matched is the same on the server and in the browser and holds no file path, so a page may render or serialise it. It also has status, error and renderMode. status is the error page's status while one is shown, else the status a loader set with data(…, { status }), else 200; a status a component sets with setResponseStatus is not reflected, so the server and the browser agree. error is what useError() gives, undefined outside an error page. renderMode is "prerendered" for a page the build wrote, "spa" for an SPA shell mounted in the browser and "ssr" otherwise, a client navigation of a server-rendered page included; on a static site a navigation that read a payload file says "prerendered". They are set on the server and again after each navigation. state is the object stored with the history entry on screen by navigate(to, { state }) or pushState ({} when none, and on the server), key that entry's history key ("" on the server) and navigationType how it was reached: "initial" for the first page of the document (a reload too, which keeps state), "push", "replace" or "pop". It must be called inside a Silgi app or it throws.

useRoute(name) gives the route with the params and search of route name (useRoute("blog-slug").params.slug is a string); name is one of the route names silgi typegen wrote, else a string. It is type-only: it checks nothing at run time and never throws, so a page that is a child of name, a copy of it (an i18n locale) or on its way out still reads its route. Because of that, route.name keeps the type of any route name. useRoute<"blog-slug">() and useRoute<Route.Name>() (06 §10.1) are the same without an argument. useRoute() stays the union over all routes, narrowed by route.name, and ReturnType<typeof useRoute> is that union. A component that wraps Link types its prop as LinkTarget<T> (NavItem<T extends string>(props: { to: LinkTarget<T> }); Vue generic="T extends string", Svelte generics="T extends string"), which checks as Link does.

ReactPreactVueSolidSveltehtml
returnsRouteValueRouteValuereactive RouteValueAccessor<RouteValue>an object of gettersRouteValue of this render

useRouter

navigate(to, { replace?, scroll?, external?, document?, shallow?, state?, refreshAll?, refresh?, keepFocus? }) and its alias push, back(), forward(), go(delta), replace(to, { scroll?, external? }), prefetch(to, { data? }), refresh(), revalidation, resolve(to), beforeEach(guard), afterEach(hook), onError(handler), isReady(), getRoutes(), hasRoute(name), currentRoute, canGoBack(), pushState(url, state) and replaceState(url, state). A target on another origin (https://other.site, //other.site, \\other.site) rejects with a TypeError unless external: true is given, which loads it in full; a scheme that is not in app.linkProtocols always rejects with a SilgiError of status 400. shallow: true changes only the URL and useRoute() when the target is the page on screen. refreshAll: true runs every loader of the target, past a prefetched answer and the client data cache, and refreshes every mounted useAsyncData; refresh: [key] treats those loader dependencies as changed for this navigation, so a page on screen or a cached entry that declared one loads again; keepFocus: true leaves the focus where it is. state (also replace(to, { state })) is a serializable object (04 §4, at most 65 536 characters serialized; the call rejects with a TypeError otherwise, before any guard) kept with the new history entry and read as useRoute().state; it survives a reload. Treat it as input: the user can replay or edit history entries. prefetch(to, { data? }) resolves, never rejects, to { type: "loaded", data } (a load ran; data is the data of the load files that ran for this prefetch, so a layout or parent route the screen keeps is not in it), { type: "redirect", location } or { type: "error", error } (a load redirected or threw), { type: "chunks" } (the chunks are loaded and no load ran: data: false, no load on the route, or a static build whose payload file says to reload) or { type: "skipped" } (a target on another origin, no page, a malformed path, a route name or params that match no page, or a prefetch cancelled by a navigation to the same page); a chunk that fails to load gives error when no load ran. A prefetch of a URL a navigation is already loading joins that load and runs none. The load result is held 30 seconds and the next navigate() to the same URL uses it once and runs no second load; a redirect or error result is not held. data: false loads the chunks only and calls no load and no handler. canGoBack() is false on the first entry of the session. pushState(url, state) (it throws for such a state, and for a scheme app.linkProtocols does not list) adds a history entry at the app path url ("" keeps the URL) holding state, and replaceState(url, state) replaces the one on screen: no guard, middleware, loader or request runs, whatever page url matches, and the page on screen stays with useRoute().state set; back from it restores the earlier state, forward to it sets it again, and a reload of it loads its URL as a document. to is an app path, put under app.baseURL like a Link, or a RouteLocation ({ name, params }), which already carries the base; a string to of navigate, push, replace and prefetch is a typed path (LinkTarget), and navigate, push and replace take an external target only with external: true. refresh() fetches the loader data of the page on screen again and commits it in place, with no history entry, no scroll and the component state kept; two calls in a row share one request, and a call while a navigation or a submission runs does nothing. refresh(key) and refresh([key, …]) re-run only the load files that declared one of the keys with depends (or fetched it with ctx.fetch) and refresh the async-data entries of those keys, and wait for a running navigation. refresh(fn, input) refreshes the entry of one handler call. revalidation is "loading" while refresh() runs and "idle" otherwise; it is read when you access it, so use useNavigation, which follows it, for a view. resolve(to) gives { href, path, name?, params, query, hash } of an app path or a { name, params } location without navigating. beforeEach(guard) runs guard(to, from) on every client navigation after route middleware, in registration order: false cancels it (the URL and page stay; a back or forward is undone), an app path or a { name, params } location redirects, a throw shows the error page. afterEach((to, from, failure?) => …) runs once per navigation, after the commit, or with failure.type "cancelled" or "superseded"; a hash-only navigate, a shallow one and refresh() run neither. onError((error, to) => …) gets every error a navigation shows or reloads for, before the error page. Each of the three returns its remover. isReady() resolves once the first page has hydrated or mounted. getRoutes() lists { name, path } of every page route (no meta: it lives in the page module), hasRoute(name) checks one, currentRoute is the route on screen; there is no addRoute. On the server prefetch resolves to { type: "skipped" }, refresh and isReady() resolve, go and the three registrations do nothing, canGoBack() is false, resolve, getRoutes, hasRoute and currentRoute work, and revalidation is "idle"; the other calls throw "only available in the browser". In html the calls throw useRouter() navigates only in the browser on the server.

ReactPreactVueSolidSveltehtml
returnsRouterApiRouterApiRouterApiRouterApiRouterApiRouterApi
import { useRoute, useRouter } from "@silgi/react";
import type { RouteValue, RouterApi } from "@silgi/react";

const title = (route: RouteValue) => `${route.name}: ${route.path}`;

export default function Toolbar() {
  const route = useRoute();
  const router: RouterApi = useRouter();
  return (
    <div>
      <h1>{title(route)}</h1>
      <p>{String(route.params.slug ?? route.query.q ?? route.hash)}</p>
      <button type="button" onClick={() => router.back()}>Back</button>
      <button type="button" onClick={() => router.navigate("/search", { replace: true, scroll: false })}>Search</button>
      <button type="button" onMouseEnter={() => router.prefetch("/pricing")} onClick={() => router.replace("/pricing")}>Pricing</button>
    </div>
  );
}

useNavigation

{ state, to?, type?, from?, complete?, revalidation? }: whether a navigation is under way. While a navigation loads, type, from and complete are those of its Navigation. state is "idle" or "loading". revalidation is "loading" while refresh(), or the refresh after a handler call, runs. A handler call from a Form or useAction is not a navigation: its state is useAction(fn).pending, useForm(fn).pending and useFormStatus().

ReactPreactVueSolidSveltehtml
returnsNavigationStateNavigationStateReadonly<ShallowRef<NavigationState>>Accessor<NavigationState>NavigationState (getters)NavigationState of this render
import { useNavigation } from "@silgi/react";
import type { NavigationState } from "@silgi/react";

const busy = (navigation: NavigationState) => navigation.state === "loading";

export default function Progress() {
  const navigation = useNavigation();
  return busy(navigation) ? <div role="progressbar">Going to {navigation.to}</div> : null;
}

onBeforeNavigate, onNavigate, onAfterNavigate

Run a function on each client navigation while the component is mounted (03 §9.2), with the Navigation. onBeforeNavigate(fn) runs before the guards of every navigation but the first, including leave when the document unloads; navigation.cancel() stops it, keeping the URL and the page (a back or forward is undone; for leave the browser asks the user to stay). onNavigate(fn) runs once the chunks and data are in, just before the new page is put on screen; a promise it returns is awaited. onAfterNavigate(fn) runs after the commit and the scroll, and once after the first page hydrated or mounted with type: "enter". The order of one navigation is page:beforeNavigate, onBeforeNavigate, the guards, page:start, chunks and data, onNavigate, the history entry, the update, page:finish, the scroll, page:rendered, onAfterNavigate, afterEach. On the server they do nothing.

function onBeforeNavigate(fn: (navigation: Navigation) => void): void
function onNavigate(fn: (navigation: Navigation) => void | Promise<void>): void
function onAfterNavigate(fn: (navigation: Navigation) => void): void
ReactPreactVueSolidSveltehtml
registersin a layout effectin a layout effectonMountedonMountonMountat once
removedon unmounton unmounton unmounton cleanupon destroyby the returned remover
onBeforeNavigate((navigation) => {
  if (dirty && !confirm("Leave without saving?")) navigation.cancel();
});
onAfterNavigate((navigation) => analytics.page(navigation.to?.path));

disableScrollHandling

Keeps the scroll position of the client navigation that runs (03 §9.7): call it in onNavigate, onAfterNavigate or the setup of the page being navigated to. The same function as disableScrollHandling of silgi/app, re-exported by every adapter.

function disableScrollHandling(): void

onBeforeRouteLeave, onBeforeRouteUpdate

Guard a navigation from a component while it is mounted (03 §9.3, step 3 of the guards: after onBeforeNavigate and the blockers, before route middleware and beforeEach). onBeforeRouteLeave(fn) runs when the navigation leaves the route of the page the component belongs to (innermost route first); onBeforeRouteUpdate(fn) when that route stays with new own params or a new query (outermost first), also for a shallow navigation. fn(to, from) answers as a beforeEach guard: false cancels (the URL and the page stay; a back or forward is undone with history.go), an app path or a { name, params } location redirects, a promise is awaited, a throw shows the error page. A hash-only navigation and a back or forward between entries of the page on screen run neither. A guard in a layout or the app root never runs; in html, where a page script registers it, it belongs to the page on screen when it registers. On the server they do nothing.

function onBeforeRouteLeave(fn: (to: ResolvedRoute, from: ResolvedRoute) => NavigationGuardResult | Promise<NavigationGuardResult>): void
function onBeforeRouteUpdate(fn: (to: ResolvedRoute, from: ResolvedRoute) => NavigationGuardResult | Promise<NavigationGuardResult>): void
ReactPreactVueSolidSveltehtml
registersin a layout effectin a layout effectonMountedonMountonMountat once
returnsvoidvoidvoidvoidvoidits remover
onBeforeRouteLeave(() => !dirty || confirm("Leave without saving?"));

useBlocker, usePreventNavigate, Blocker, BlockerArgs, UseBlockerOptions

Hold navigations away from the page while the component is mounted, usually while a form is dirty (03 §9.4). useBlocker(shouldBlock) (or { shouldBlock, beforeUnload = true }) asks shouldBlock({ from, to, type }) before each navigation; when it is true, the navigation waits with state "blocked" and location the target route (undefined for a target that is no page) until you call reset() (it is cancelled: the URL and the page stay, a back or forward is undone, state turns "unblocked") or proceed() (it goes on: state is "proceeding", then "unblocked" once it ends). With beforeUnload, closing or reloading the tab while shouldBlock({ from, to: null, type: "leave" }) is true shows the browser's own dialog; the listener is there only while it holds (asked again after each render, on input and after each navigation), and a form submission's own page load never shows it. A blocked navigation that another one replaces (a navigation let through, refresh(), a submission) turns state back to "unblocked"; a redirect of a navigation the blockers let go is not asked again. While a blocker waits, useNavigation() stays idle. usePreventNavigate(fn) registers fn itself: true blocks, false lets go, a promise is awaited. Blockers run after onBeforeNavigate and before onBeforeRouteLeave; every one must let a navigation go, the first that blocks ends the list. A form's navigation (a GET form, a submission and the navigation after it, type: "form") is never blocked. On the server they do nothing.

interface BlockerArgs { from: ResolvedRoute; to: ResolvedRoute | null; type: NavigationType }
interface UseBlockerOptions { shouldBlock: (args: BlockerArgs) => boolean; beforeUnload?: boolean }
interface Blocker { state: "unblocked" | "blocked" | "proceeding"; location?: ResolvedRoute; proceed(): void; reset(): void }
function useBlocker(options: ((args: BlockerArgs) => boolean) | UseBlockerOptions): Blocker
function usePreventNavigate(fn: (args: BlockerArgs) => boolean | Promise<boolean>): void
ReactPreactVueSolidSveltehtml
useBlocker returnsBlockerBlockerReadonly<ShallowRef<Blocker>>Accessor<Blocker>Blocker (getters)Blocker (getters) with subscribe(listener) and remove(), registered at once
usePreventNavigate returnsvoidvoidvoidvoidvoidits remover

React and Preact read the newest shouldBlock of each render; Vue, Solid and Svelte the one given at setup, so read reactive state inside it. In html, useBlocker, usePreventNavigate, onBeforeRouteLeave, onBeforeRouteUpdate and useSnapshot called while a page renders in the browser are removed at the next paint (the next page or a repaint).

useSnapshot, SnapshotOptions

Keep a value of the page per history entry, such as text typed into a form, while the component is mounted (03 §9.6). When the page leaves an entry, capture() is stored under that entry and key; when back, forward or a reload of the tab brings the entry back, restore(value) runs once the page is on screen, or when the component mounts later (client-only, lazy, Suspense) before the next navigation. Values must be payload-serializable (04 §4) and at most 65 536 characters serialized; one that is not is logged and skipped. capture() decides what is stored in plain sessionStorage: never return secrets. A cookie change drops every stored value. They are kept in memory and, when the page is hidden, in sessionStorage item silgi:snapshot, for the last 100 entries; with no storage they live in memory only. On the server it does nothing.

interface SnapshotOptions<T> { capture: () => T; restore: (value: T) => void }
function useSnapshot<T>(key: string, options: SnapshotOptions<T>): void
ReactPreactVueSolidSveltehtml
registersin a layout effectin a layout effectonMountedonMountonMountat once
returnsvoidvoidvoidvoidvoidits remover
const note = useRef<HTMLTextAreaElement>(null);
useSnapshot("note", {
  capture: () => note.current?.value ?? "",
  restore: (value) => void (note.current && (note.current.value = value)),
});
const blocker = useBlocker(() => dirty);
{blocker.state === "blocked" && (
  <dialog open>
    Leave without saving? <button onClick={blocker.proceed}>Leave</button> <button onClick={blocker.reset}>Stay</button>
  </dialog>
)}

usePoll, useOnline

usePoll(ms, { keys? }) calls useRouter().refresh(keys) every ms milliseconds while the document is visible and the browser is online, and stops when the component unmounts. Hiding the document stops the timer and showing it starts a new full interval; a tick while offline is skipped; a refresh that fails on the network keeps the page. Without keys it refreshes the loaders and the live useAsyncData entries; with keys it re-runs only the loaders that declared one of them. ms below 1000 throws RangeError: usePoll() needs at least 1000 ms (got 200) where the hook is called. On the server it starts nothing. useOnline() is true on the server and during hydration, then navigator.onLine, following the online and offline events.

ReactPreactVueSolidSveltehtml
usePoll returnsvoidvoidvoidvoidvoid() => void, the stop function
useOnline returnsbooleanbooleanReadonly<Ref<boolean>>Accessor<boolean>{ readonly value } (getter){ readonly value }, read when accessed
usePoll(5000, { keys: ["/api/todos"] });
const online = useOnline();
return online ? null : <p>You are offline.</p>;

In html there is no unmount: keep the stop function and call it to end the poll. A second usePoll with the same ms and keys in the same app returns the running poll's stop function, so a page function that paints again does not start another timer; a navigation that leaves the page stops its polls, and on the server nothing is kept.

function usePoll(ms: number, options?: { keys?: string[] }): void // html: () => void
function useOnline(): boolean // see the table for the other shapes

useSearch

The validated search of the page on screen (useRoute().search) and a way to write it. useSearch(select?) returns { value, set }: value is the search, or select(search) when given; a selection is compared by deep equality, so a component re-renders (react, preact) or a computed fires (vue, solid, svelte) only when the selected value changes. set(next | (prev) => next, { replace = true }) navigates to the page on screen with that search, written through the search codec; a key is left out of the URL when validating the search without it gives the same value, so a key at its default never shows. replace: false pushes a history entry. The navigation runs guards and the loaders whose searchKeys changed. useRouter().navigate({ query: (prev) => next }) does the same without the hook.

ReactPreactVueSolidSveltehtml
valuethe valuethe valueComputedRefAccessorgetterthe value at render
const page = useSearch((search) => search.page);
return <button onClick={() => page.set((prev) => ({ ...prev, page: prev.page + 1 }))}>{page.value}</button>;

Link takes query={(prev) => next} in all six: the href holds the query next makes from the search of the page on screen, with to as the path, and the link is marked data-silgi-trim. Clicking a marked link to the page on screen leaves out the keys at their default; the href itself keeps the whole query, which is correct and only longer, also with an asynchronous search schema.

function useSearch(select?: (search: Search) => S): { value: S; set(next: Search | ((prev: Search) => Search), options?: { replace?: boolean }): Promise<void> }

useOptimistic

useOptimistic(value, reduce) returns reduce(value, pending) while handler calls are pending, and value otherwise. pending is a list of PendingRun, oldest first: every running useAction and Form action={fn} call ({ fn, instance?, input, formData? }). An entry stays until the refreshed data commits, so the value never falls back between the click and the new data; a fail(), an error or a redirect drops it at once. React passes the result through React's own useOptimistic. html has no useOptimistic; its scripts read pendingRuns(app) of silgi/adapter.

ReactPreactVueSolidSvelte
valueTTMaybeRefOrGetter<T>T | Accessor<T>T | () => T
returnsTTComputedRef<T>Accessor<T>{ readonly value: T }

useAction, UseActionResult

Calls a handler (defineHandler, see silgi/server) from a component and keeps its pending state, last value, last fail() and last error. run(input) calls the function and returns its value; calls of one hook run one after another, in the order of the run calls, and a failing call does not stop the ones behind it. After a success the route's loaders are fetched again (useRouter().refresh()) unless refresh: false; pending stays true until that has finished. state starts as initial (after a post without JavaScript to a Form action={fn} of this function, as that post's result, failure and error too) and is the value of the last successful call, failure the { status, data } of the last fail() (not an error, and nothing is refreshed), error the SilgiError of the last failed call; the next call clears both; a redirect the function returns or throws is not an error (error and onError stay as they were, run rejects with the redirect). reset() returns state to initial, clears error, aborts the call that runs (its signal) and drops the ones waiting; none of them sets state or error, calls a callback or refreshes. A callback that throws is logged.

function useAction<I, O>(fn: Handler<I, O>, options?: { initial?: O; onSuccess?(value: O): void; onError?(error: SilgiError): void; refresh?: boolean; instance?: string | number }): { run(input: I): Promise<O>; state: O | undefined; pending: boolean; error: SilgiError | undefined; failure: ActionFailure | undefined; reset(): void }
ReactPreactVueSolidSveltehtml
returnsobject, re-rendersobject, re-rendersreactive objectrun, reset and an Accessor per fieldobject of gettersobject at render, with subscribe(listener)
const add = useAction(addTodo);
return <button disabled={add.pending} onClick={() => void add.run("Milk")}>Add</button>;

instance keeps one store per (function id, instance); Form action={fn} instance={key} is the same store. UseActionResult<I, O> is the return type in React, Preact and Vue; Solid and Svelte return their own shapes. run rejects when the handler fails, so a call that does not read error ends with .catch(() => {}). On the server state is initial and pending is false. An html page has no component to keep the state in: useAction(fn) returns the same runner for the same function in one app, its options are the ones of the last call, and a script that wants to paint again calls subscribe.

useFormStatus

The running submission of the Form the component is rendered in: { pending, data, action }, pending true from the submit until the fresh data is on screen, data the FormData that was posted, action the app path and query it posted to. Outside a Form, for a form that is not posting and on the server it is { pending: false, data: null, action: null }. html has no useFormStatus: the navigator sets data-pending on a data-silgi-form form while it posts, for CSS (form[data-pending] button { opacity: .5 }) or a page script.

function useFormStatus(): { pending: boolean; data: FormData | null; action: string | null }
ReactPreactVueSolidSvelte
returnsobjectobjectreactive object{ pending, data, action } of Accessorsobject of getters
function SaveButton() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>Save</button>;
}

useForm

The field helpers and the runs of one handler: createForm(fn, options) over the adapter's state. useForm(fn) returns { fields, pending, result, for(key) }. Spread fields.title.as("text") on an input to get name, type, the value of the last failed post and aria-invalid="true" while the field has an issue; fields.title.issues() lists them. fields is typed from fn's input, result is what the last run returned (a fail() included), pending is true while a run of the handler is in flight, and for(key) is the store of useAction(fn, { instance: key }): with several forms of one handler on a page, give each Form action={fn} instance={key} and read form.for(key).pending and .result. A field's kind comes from as(). On the server pending is false.

function useForm<I, O>(fn: Handler<I, O>): { fields: FieldsOf<I>; pending: boolean; result: O | undefined; for(key: string | number): { fields; pending; result } }
ReactPreactVueSolidSveltehtml
pending, resultvaluesvaluesvalues (read in a render or a computed)accessorsgettersvalues at render
attributesdefaultValue, defaultChecked (uncontrolled)value, checkedvalue, checkedvalue, checkedvalue, checkedvalue, checked, written by you as name="value"

React gives defaultValue and defaultChecked where the others give value and checked, because React reads a value without onChange as a read-only field; the server HTML has value="kept" all the same. html has no helper that writes the attributes: build the string from the object.

const form = useForm(sendContact);
return <Form action={sendContact}><input {...form.fields.email.as("email")} />{form.fields.email.issues().map((issue) => <p>{issue.message}</p>)}</Form>;

useData

The data of the loader of the route that renders the component, unknown unless you pass the type. A route with no loader gets null. A layout without a load and the root component get null, and a parent route never sees the data of its child.

function useData<T = unknown>(...unknownRoute: UseDataArgs<T>): RouteLoaderData<T>

With the types silgi typegen wrote, a route name as the type argument gives that route's loader data: useData<"blog-slug">().title is what the blog-slug loader returns, and useData<"nope">() is a type error. Any other type argument is the data type, as before: useData<{ a: string }>().

ReactPreactVueSolidSveltehtml
returnsTTComputedRef<T>Accessor<T>{ readonly value: T }T

T in the table is RouteLoaderData<T>.

import { useData } from "@silgi/react";

export default function Post() {
  const { title, body } = useData<{ title: string; body: string }>();
  return (
    <article>
      <h1>{title}</h1>
      <p>{body}</p>
    </article>
  );
}

useRouteData, useMatches, MatchedRoute, RouteDataName, RouteDataOf

The explicit ways to read the loader data of a route other than your own: useData() stays scoped to the route that renders the component, and a layout or a parent page never sees its child's data through it. useRouteData(name) gives the loader data of the route name while that route is in the chain on screen, and undefined for a route that is not (a name that is no route included), so another page's data is never exposed. useMatches() gives useRoute().matched, outermost first, with each entry's params (the ones its path names) and data (its loader's data, undefined without a loader); layouts are not in the list. With the types silgi typegen wrote, name is checked against the routes that have a loader and the result is LoaderData<name> | undefined, so useRouteData("nope") is a type error; before that name is a string and the result unknown. The data of a MatchedRoute is loosely typed (any): check name before reading it. Both follow client navigations, a route kept across one, refresh() and invalidate().

function useRouteData<N extends RouteDataName>(name: N): RouteDataOf<N>
function useMatches(): MatchedRoute[]
ReactPreactVueSolidSveltehtml
useRouteData returnsTTComputedRef<T>Accessor<T>{ readonly value: T }T
useMatches returnsMatchedRoute[]MatchedRoute[]ComputedRef<MatchedRoute[]>Accessor<MatchedRoute[]>{ readonly value: MatchedRoute[] }MatchedRoute[]

T in the table is RouteDataOf<N>. MatchedRoute is an entry of route.matched with params and data; RouteDataName and RouteDataOf are the types of useRouteData (see silgi/adapter).

function Layout({ children }) {
  const titles = useMatches().map((match) => match.data?.title);
  return <header>{titles.join(" / ")}{children}</header>;
}

Errors

useError

The error being shown, or undefined. An error page reads it. Every adapter types it with ErrorPageProps of silgi/app: { status, message, data?, route?, stack? }. Once a page exports Errors, the type is RouteError: checking error.route and error.status narrows data to what that page declares for that status.

ReactPreactVueSolidSveltehtml
returnsRouteError | undefinedRouteError | undefinedShallowRef<RouteError | undefined>Accessor<RouteError | undefined>{ readonly value: RouteError | undefined }RouteError | undefined

clearError

The function of silgi/app, re-exported by every adapter: clears the error and navigates to redirect or renders the current URL again.

function clearError(options?: { redirect?: string }): Promise<void>

showError

The function of silgi/app, re-exported by every adapter: renders the error page without changing the URL; on the server it throws, so the error page answers.

function showError(error: unknown): void

The function of silgi/app, re-exported by every adapter.

navigateTo(to, options?) goes to to, an app path, a URL or a route location. In the browser it calls navigate, or replace with replace: true, and resolves when the navigation ends; a target on another origin throws a TypeError unless external: true; document: true loads the target with location.assign (location.replace with replace) instead. During server rendering, and in route middleware on the server and in the browser, it throws the redirect that redirect(to, { status: redirectCode, external }) returns, so return navigateTo("/login") and await navigateTo("/login") both end the middleware; redirectCode defaults to 302. With open: { target, windowFeatures? }, in the browser it calls window.open(href, target, features) instead and navigates nowhere: href is the target under app.baseURL, and the features are key=value joined with ,, true written yes and false written no ({ noopener: true, width: 400 } is "noopener=yes,width=400"), led by noopener=yes,noreferrer=yes unless windowFeatures names noopener or noreferrer, so the new window gets no window.opener; the external rule still applies. On the server open is ignored (dev logs [silgi] navigateTo() open is ignored on the server).

function navigateTo(to: string | RouteLocation, options?: { replace?: boolean; external?: boolean; document?: true; redirectCode?: number; open?: { target: string; windowFeatures?: Record<string, string | number | boolean> } }): Promise<void>

App

useApp

The app context. Returns AppContext in every library.

useRuntimeConfig

The client runtime config, { public, app }, as Nuxt's: useRuntimeConfig().public.siteName is the public runtime config, typed from runtimeConfig.public once silgi typegen ran, and useRuntimeConfig().app is { baseURL, buildAssetsDir }. The private part never reaches the browser; read it with useRuntimeConfig(event) of silgi/server.

useAppConfig, updateAppConfig

The merged app config, app.config.ts over appConfig. It is public. updateAppConfig(patch) (the same function as silgi/app, without app) deep-merges a patch into it; the components that read useAppConfig() re-render (html has no re-render: it reads the new config at its next render). On the server, useAppConfig() of silgi/server is the per-request copy. Its type is ConfigShape<SilgiAppConfig> of silgi/app: the generated shape once silgi typegen ran, so a key the config does not have fails tsc, and Record<string, unknown> before.

function useAppConfig(): ConfigShape<SilgiAppConfig>
import { useApp, useAppConfig, useRuntimeConfig } from "@silgi/react";

export default function Footer() {
  const app = useApp();
  const config = useRuntimeConfig();
  const settings = useAppConfig();
  return (
    <footer>
      {String(config.public.apiBase)} {String(settings.title)} {Object.keys(app.state.dump()).length}
    </footer>
  );
}

useHead

Pushes a HeadInput for as long as the component lives. In the browser the entry is pushed when the component commits and removed when it unmounts, or when it never commits. The same input rules apply in every library.

ReactPreactVueSolidSveltehtml
takesHeadInputHeadInputMaybeRefOrGetter<HeadInput>, refs, computeds and getters also as any leafHeadInput | (() => HeadInput)HeadInput | (() => HeadInput)HeadInput

useSeoMeta

useHead with the flat keys of SeoMetaInput. It takes the same forms as useHead of its library. In Vue a ref, a computed or a getter works at any depth (useHead({ title: ref("A"), meta: [{ name: "x", content: computed(() => "y") }] })), and a change to one patches the entry; a titleTemplate function is kept as a function.

import { useHead, useSeoMeta } from "@silgi/react";

export default function Pricing() {
  useHead({ title: "Pricing", htmlAttrs: { lang: "en" } });
  useSeoMeta({ description: "Plans and prices", ogImage: "/og.png" });
  return <h1>Pricing</h1>;
}

State and cookies

useState

A value in the shared state, keyed by name: the server's value is in the browser after hydration. init is a value or a function and is used only while the key is missing.

function useState<T>(key: StateKey<T>): ...   // defineState of silgi/app
function useState<T>(key: string, init?: T | (() => T)): ...
function useState<T>(init?: () => T): ...   // the key is added by the build

A StateKey from defineState carries the key, its type and its init, so useState(userState) is typed with no type argument and takes no second argument.

Without a key, useState(() => 1) takes one from the build: a call imported from the adapter gets { [Symbol.for("silgi.key")]: "$s<8 hex>" } appended (build.keyedComposables). One call site is one key, so a component rendered twice shares the value. A call the build cannot see (a function that is not listed, a call through a variable) throws useState() needs a key; pass one or list the function in build.keyedComposables. A string as the first argument is always the key.

ReactPreactVueSolidSveltehtml
returns[T, (value: T | (previous: T) => T) => void]the sameRef<T>[Accessor<T>, (next: T | (prev: T) => T) => T]{ value: T }{ value: T }, not reactive

useCookie

A cookie as a value of type T (default string). It takes the name and the options of setCookie of silgi/server, plus default, encode, decode and readonly. The value is JSON by default: set({ theme: "dark" }) writes %7B%22theme%22%3A%22dark%22%7D and a read gives the object back; a raw cookie that is not JSON (abc) reads as the string abc, and set("abc") writes %22abc%22. default: () => value is the value while the cookie is absent and writes nothing until you set. encode and decode replace the codec (encode: String, decode: String for raw strings; the URI encoding stays with the cookie jar). readonly: true makes setting throw a TypeError. signed: true signs the cookie with the app key and reads only a verified value; the browser cannot sign, so setting it there throws TypeError: signed cookies are written on the server only (see useCookie in the silgi/app reference). Setting undefined deletes the cookie. The type is T | undefined, and T when default is given.

ReactPreactVueSolidSveltehtml
returns[T | undefined, (value: T | undefined) => void]the sameRef<T | undefined>[Accessor<T | undefined>, (next: T | undefined) => void]{ value: T | undefined }{ value: T | undefined }

refreshCookie

refreshCookie(name) re-reads document.cookie and updates every useCookie(name) that read a different value; it is the function of silgi/app without app, and does nothing on the server.

UseCookieWatchOptions

Vue only: useCookie(name, { watch: true | "shallow" | false }), default true. true writes the cookie when a property of an object value changes; "shallow" writes only when .value is assigned; false never writes (an assignment changes the ref until the cookie itself changes).

interface UseCookieWatchOptions { watch?: boolean | "shallow" }
import { useCookie, useState } from "@silgi/react";

export default function Settings() {
  const [count, setCount] = useState("clicks", 0);
  const [theme, setTheme] = useCookie("theme", { maxAge: 31536000 });
  return (
    <div>
      <button type="button" onClick={() => setCount((value) => value + 1)}>{count}</button>
      <button type="button" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>{theme ?? "system"}</button>
    </div>
  );
}

Request

useRequestEvent, useRequestHeaders, useRequestHeader, useRequestURL, useRequestFetch

Read the request being rendered, in a component. They are the functions of silgi/app (see Request there) without the app argument, and they return plain values in every adapter.

function useRequestEvent(): SilgiEvent | undefined
function useRequestHeaders(names?: readonly string[]): Record<string, string>
function useRequestHeader(name: string): string | undefined
function useRequestURL(): URL
function useRequestFetch(): $Fetch

On the server they give the event, the request's headers (lower-cased, only names when given), one header, getRequestURL(event) and the request-bound $fetch; in the browser undefined, {}, undefined, new URL(location.href) and $fetch. They throw outside a Silgi app, like useApp.

setResponseStatus, useResponseHeader, HttpStatusCode, HttpHeader

Set the status and headers of the document from a component. They are the functions of silgi/app (see Request there) without the app argument. setResponseStatus(status, text?) and useResponseHeader(name) are in every adapter; <HttpStatusCode code text?> and <HttpHeader name value> render nothing and make the call during the server render, in every adapter but html. In the browser all of them do nothing.

function setResponseStatus(status: number, statusText?: string): void
useResponseHeader(name)ReactPreactVueSolidSveltehtml
returns[string | undefined, (value: string | undefined) => void]the sameRef<string | undefined>[Accessor<string | undefined>, (value: string | undefined) => void]{ value: string | undefined }{ value: string | undefined }

clearState, callOnce

clearState(keys?, { reset = true }) and callOnce(key, fn, { mode }) are the functions of silgi/app (see State and cookies and callOnce there) without the app argument; they call useApp(), so they work only while a component renders or sets up (not in an event handler) and throw outside a Silgi app. In a handler, read const app = useApp() during render and call clearState(app, …) or callOnce(app, …) of silgi/app. callOnce returns the same promise.

function clearState(keys?: string | StateKey<unknown> | Array<string | StateKey<unknown>> | ((key: string) => boolean), options?: { reset?: boolean }): void
function callOnce(key: string, fn: () => unknown | Promise<unknown>, options?: { mode?: "render" | "navigation" }): Promise<void>

Data in components

useAsyncDataEntry, useNuxtData

The data of the async-data entry key, whichever component started it (useFetch(url, { key }), useAsyncData(key, …)), and a way to write it. Writing replaces data and tells the entry's subscribers without changing its status or error, an optimistic update that the next run of the handler replaces. A key with no entry reads undefined, and writing it does nothing. useNuxtData is Vue's name for the same function.

function useAsyncDataEntry<T = unknown>(key: string): ...
ReactPreactVueSolidSveltehtml
returns{ data: T | undefined, setData(data: T): void }the same{ data: Ref<T | undefined> }{ data: Accessor<T | undefined>, setData(data: T): void }{ data: T | undefined }, a getter and a setter{ data: T | undefined }, read at render

useAsyncData, useFetch, UseAsyncDataOptions, UseAsyncDataResult, UseFetchOptions, PickFrom

useAsyncData(fn, input?, options?) calls a handler of the app (imported from #server) when a component using the call mounts; the key is made from the handler and the input, input may be a function that returns it, and components with the same call share the entry. useAsyncData(key, handler, options?) runs handler for a key you write, for work that is not a handler call; useFetch(url, options?) is useAsyncData with $fetch and a key made from the URL and the options. Both return { data, error, status, pending, refresh(), execute(), clear() }; pending is status === "pending", and also true for an idle entry with no data under app.asyncData.pendingWhenIdle: true (default false); execute is refresh, and clear() is clearAsyncData for the result's own key (the entry goes back to idle). Options are those of AsyncDataOptions (server, lazy, default, transform, immediate, dedupe) plus pick, watch and getCachedData; useFetch also takes key and every FetchOptions field (UseFetchOptions). pick: ["id"] keeps only those top-level keys of an object result, after transform, in the stored data, the payload and data, and types data as PickFrom<T, K> (name the keys in the type arguments, useFetch<Post, Post, "id">, or let a typed useFetch("/api/x", { pick }) infer them). useFetch(url, { key }) uses key as the store key instead of the one made from the URL and the options, and key is not sent with the request. In Vue, a url, query or body that is a ref or a getter re-fetches when it changes, also with a key; watch: false turns that off, and refresh() still reads the current values. The other adapters never watch implicitly, so watch: false is accepted and does nothing there (html has no watch list). The handler gets { signal }; useFetch passes it to $fetch, combined with its own signal option. refresh(), execute() and watch run the handler of the latest render in React and Preact.

In a production build the error is the createError the handler threw, with its status, message and data. Any other error is masked: it becomes a 500 Internal Server Error (or its own HTTP status with that status's text) with no data, in the server HTML and in the payload alike, and the original is logged on the server. Throw createError for a message the visitor may see. While the server renders, useFetch of a URL that starts with / forwards the request's cookie and authorization headers, and the answer goes into the payload.

key, url and useFetch's query and body may be getters (Vue: a ref or a getter; refs nested at any depth in query and body are unwrapped). When a getter's value changes, as on /blog/a → /blog/b with the same page instance, the composable moves to the new key: it fetches the new entry, its values follow it, and refresh() acts on it.

getCachedData(key, app, { cause }) is a GetCachedData<T>; cause is a GetCachedDataCause. A value other than undefined becomes the data and the handler does not run. The default returns the server's data while the page hydrates and undefined otherwise, so a page visited again after a client navigation fetches again; return app.asyncData.get(key)?.data to keep the data fetched earlier.

function useAsyncData<I, R, T = R, K extends keyof T & string = never>(fn: Handler<I, R>, input?: I | (() => I), options?: UseAsyncDataOptions<T, R, K, undefined>): UseAsyncDataResult<PickFrom<T, K>>   // and the overload with default
function useAsyncData<T, R = T, K extends keyof T & string = never, D = T>(key: string | (() => string), handler: (ctx: { signal: AbortSignal }) => R | Promise<R>, options: UseAsyncDataOptions<T, R, K, D> & { default: () => D }): UseAsyncDataResult<PickFrom<T, K>, D>
function useAsyncData<T, R = T, K extends keyof T & string = never>(key: string | (() => string), handler: (ctx: { signal: AbortSignal }) => R | Promise<R>, options?: UseAsyncDataOptions<T, R, K, undefined>): UseAsyncDataResult<PickFrom<T, K>>
function useAsyncData<T, R = T, K extends keyof T & string = never>(handler: (ctx: { signal: AbortSignal }) => R | Promise<R>, options?: UseAsyncDataOptions<T, R, K, undefined>): UseAsyncDataResult<PickFrom<T, K>>   // the key is added by the build, and the overload with default
function useFetch<U extends string, M extends string = "GET", K extends keyof FetchData<U, M> & string = never, D = undefined, B extends string | undefined = undefined>(url: U | (() => U), options?: UseFetchOptions<FetchData<U, M>, FetchData<U, M>, K, D> & FetchCall<U, M, B>): UseAsyncDataResult<PickFrom<FetchData<U, M>, K>, D>
function useFetch<T, R = T, K extends keyof T & string = never, D = T, U extends string = string, M extends string = "GET", B extends string | undefined = undefined>(url: U | (() => U), options: UseFetchOptions<T, R, K, D> & { default: () => D } & FetchCall<U, M, B>): UseAsyncDataResult<PickFrom<T, K>, D>
function useFetch<T, R = T, K extends keyof T & string = never, U extends string = string, M extends string = "GET", B extends string | undefined = undefined>(url: U | (() => U), options?: UseFetchOptions<T, R, K, undefined> & FetchCall<U, M, B>): UseAsyncDataResult<PickFrom<T, K>>

UseAsyncDataResult<T, D = undefined> has data of T | D, where D is the type of default's return: useFetch<Post[]>(url, { default: () => [] }).data is Post[], without default it is Post[] | undefined. With your own T, a default that returns another type needs D named: useFetch<Post[], Post[], never, null>(url, { default: () => null }) gives Post[] | null.

The first useFetch overload types data like $fetch: with the types silgi typegen wrote, useFetch("/api/search").data is what the /api/search handler returns (FetchData of silgi/app), unknown for a URL no server route matches. A call with your own type argument, useFetch<Post>(url), or a transform that changes the type takes the second. In Vue url is a MaybeRefOrGetter<U>.

useFetch and useLazyFetch check body and query as $fetch does (RouteInput): only when url is a literal that a route matches (a getter or, in Vue, a ref of a literal counts), method is a literal or absent (without case, HEAD as GET) and the call has no baseURL and no type argument. A wrong or unknown field, a wrong type, a missing required field or a method the route does not take fails at the call, in every overload. query and body may still be a getter or accessor (in Vue also a ref, or refs inside), whose value is checked. Every other call stays loose: a string URL or method, a URL union, a UseFetchOptions variable, a UseFetchOptions with a baseURL, and every call of createUseFetch, whose defaults (method, body, query, baseURL) change what a call sends (it returns a useFetch that checks nothing). A generic wrapper that passes an object literal (<U extends string>(url: U) => useFetch(url, { headers })) fails, as for $fetch: pass a UseFetchOptions variable or call useFetch<T>. FetchCall<U, M, B> stands for { method?: M | string; baseURL?: B | string } and, for a checked call, the query or body field typed from RouteInput (FetchInputField), with that key left out of UseFetchOptions. A call that omits options is not refused when the route has a required field, as for $fetch.

ReactPreactVueSolidSveltehtml
data, error, statusplain valuesplain valuesComputedRefsAccessorsgettersgetters, plus subscribe(listener)
watchreadonly unknown[] | falsereadonly unknown[] | falseWatchSource[] | falseReadonlyArray<() => unknown> | falseReadonlyArray<() => unknown> | falsefalse
first server HTMLwaits for the datawaits for the datawaits for the datawaits for the datawaits for the datawaits for the data

Data has the details.

import { useAsyncData, useFetch } from "@silgi/react";
import type { UseAsyncDataOptions, UseAsyncDataResult } from "@silgi/react";

const options: UseAsyncDataOptions<string[]> & { default: () => string[] } = { default: () => [], lazy: true };

export default function Panels({ id }: { id: string }) {
  const tags: UseAsyncDataResult<string[], string[]> = useAsyncData("tags", async () => ["a", "b"], options);
  const user = useFetch<{ name: string }>(`/api/users/${id}`, { watch: [id], query: { full: 1 } });
  return (
    <div>
      <p>{tags.data?.join(", ")}</p>
      <p>{user.status === "success" ? user.data?.name : user.status}</p>
      <button type="button" onClick={() => void user.refresh()}>Reload</button>
    </div>
  );
}

createUseFetch, createUseAsyncData, useLazyFetch, useLazyAsyncData

createUseFetch(defaults, { $fetch }?) returns a function that behaves as useFetch and checks no body or query (the overloads of useFetch without the literal-call check), with defaults under the options of every call. defaults is an options object, or a function that gets the call's options and returns the options to use. A call's option wins over a default; headers merge by name and query by key; every object of the defaults is copied for each call. The key still comes from fetchKey of the merged options, so two created functions that differ only in their $fetch share a key unless a call passes key. $fetch replaces the request-bound client (useRequestFetch()), for example with a $fetch.create one. createUseAsyncData(defaults) does the same for useAsyncData; its defaults are UseAsyncDataOptions. useLazyFetch and useLazyAsyncData are useFetch and useAsyncData with lazy: true: on the server they wait for the data like any entry, and in the browser a navigation shows the page with pending true and does not wait. Vue's UseAsyncDataOptions gain deep: with true, an object data is deeply reactive; the default false leaves it a shallow value that changes only when the entry's data is replaced.

function createUseFetch(defaults: UseFetchOptions | ((options: UseFetchOptions) => UseFetchOptions), options?: { $fetch?: $Fetch }): UseFetchLoose   // useFetch's overloads without the body and query check
function createUseAsyncData(defaults: UseAsyncDataOptions | ((options: UseAsyncDataOptions) => UseAsyncDataOptions)): typeof useAsyncData
const useLazyFetch: typeof useFetch
const useLazyAsyncData: typeof useAsyncData
const useApi = createUseFetch({ baseURL: "/api", headers: { "x-k": "1" } });
const todos = useApi("/todos"); // GET /api/todos with x-k: 1

Shared types

Every adapter re-exports these from silgi/adapter: the navigation the three hooks above get, its type ("enter", "link", "goto", "popstate", "form", "leave") and the three function types.

RouteValue, RouterApi, NavigateOptions, NavigationState, GetCachedData, GetCachedDataCause, AsyncDataHandler, FetchRequest, ErrorBoundaryFallbackProps

Every adapter exports these under these names; each is defined once, in silgi/adapter, and the adapters re-export it. Vue exports no FetchRequest: its UseFetchOptions are its own.

NameIs
RouterApiwhat useRouter() gives: navigate(to, options?) (and push) and replace(to, options?) return Promise<void>, and prefetch(to, options?) returns Promise<PrefetchResult> (see useRouter); back(), forward() and go(delta) return void; refresh() (re-runs the load files of the page on screen and refreshes the live async-data entries) and revalidation ("loading" while it runs); resolve, beforeEach, afterEach, onError, isReady, getRoutes, hasRoute, currentRoute, canGoBack, pushState and replaceState (see useRouter)
RouteValuewhat useRoute() gives: path, fullPath, params, query, hash, name, meta, matched (RouteMatch[]), search, status, error, renderMode, state, key and navigationType
NavigateOptions{ replace?, scroll?, external?, document?, shallow?, state?, refreshAll?, refresh?, keepFocus? }, the options of navigate
NavigationState{ state: "idle" | "loading"; to?; type?; from?; complete?; revalidation? }, what useNavigation() gives; type, from and complete are set while a navigation loads
GetCachedData<T>(key, app, { cause }) => T | undefined, the getCachedData option
GetCachedDataCause"initial" (the first call), "refresh:manual" (refresh(), execute()), "refresh:hook" (router.refresh(key)), "watch"
AsyncDataHandler<R>({ signal }) => R | Promise<R>, the handler of useAsyncData
FetchRequestthe options of useFetch other than the AsyncDataOptions: FetchOptions whose query and body may be getters
ErrorBoundaryFallbackProps{ error: unknown; reset: () => void }, what an ErrorBoundary passes to its fallback

The wrappers stay each library's own: Accessor<…>, Ref<…>, ShallowRef<…>, UseAsyncDataResult, UseAsyncDataOptions, UseFetchOptions, LinkProps and ErrorBoundaryProps.

Other entries

@silgi/<library>/adapter, @silgi/<library>/error-page

@silgi/<library>/adapter default-exports the adapter silgi loads (see silgi/adapter). @silgi/<library>/error-page re-exports DefaultErrorPage.

@silgi/<library>/runtime/client, @silgi/<library>/runtime/server

The entries that the adapter object names by specifier: the code silgi puts in the browser bundle and in the server bundle. An app does not import them.