Context & Scope
Vincle has two ways to share values. Tree Context passes a value to descendants of a JSX Provider. Scope holds mutable state for one async execution, such as request metadata or Flow orchestration.
Tree Context#
createContext(default) creates a context; useContext(context) reads the
value of the nearest Provider above, or the default outside any Provider.
import { createContext, useContext, renderToString } from "@vincle/core";
const Theme = createContext<"light" | "dark">("light");
function ThemedBox({ children }: { children: string }) { const theme = useContext(Theme); return <div class={theme === "dark" ? "dark" : "light"}>{children}</div>;}
const html = await renderToString( <Theme.Provider value="dark"> <ThemedBox>Hello</ThemedBox> </Theme.Provider>,);A Provider needs no Scope.with. Its value holds across await and across
deferred Flow fragments, and concurrent renders never share it. The shape
matches React, Preact and Hono, but their context objects are not
interchangeable with vincle's.
Scope#
Scope.key<T>(name) creates a named, typed token. Scope.with(fn) runs fn in
an isolated async scope, where Scope.set(key, value) writes and
Scope.get(key) reads the last value written, so a later sibling reads what an
earlier component set. get throws outside a scope or when the key has not
been set.
import { Scope, renderToString } from "@vincle/core";
declare function getSession(req: Request): Promise<{ userId: string }>;const Request = Scope.key<{ userId: string; locale: string }>("app:request");const App = () => <main>{Scope.get(Request).userId}</main>;
async function handleRequest(req: Request): Promise<Response> { const session = await getSession(req); const html = await Scope.with(() => { Scope.set(Request, { userId: session.userId, locale: req.headers.get("Accept-Language") ?? "en", }); return renderToString(<App />); }); return new Response(html, { headers: { "Content-Type": "text/html" } });}Scope.snapshot() copies the current map; pass the copy as the second argument
of Scope.with to seed a child scope:
const childHtml = await Scope.with(async () => { Scope.set(Theme, "dark"); return Scope.with(() => renderToString(<ChildPage />), Scope.snapshot());});Runtime support#
Both stores use globalThis.AsyncLocalStorage or node:async_hooks. Without
either, they warn and support one render at a time (async included): a second
render entering a Provider or Scope.with while the first is in flight is
refused. A concurrent render that reads a context without entering one may see
the in-flight render's values. Enable AsyncLocalStorage for concurrent
rendering.