component combines Effectful setup with a renderable result. A generator with no arguments creates a component value; a generator with arguments creates a component function. Each execution owns the state and scoped work it creates. Import it from @typed/template; it does not require the UI package.
Start with your first template. Use a component when local state or service acquisition must happen before returning the view. A static template needs only html.
A component value and a component function
import { RefSubject } from "@typed/fx";
import { html, component } from "@typed/template";
const CounterNotice = html`<p>Each counter starts with its own state.</p>`;
const Counter = component(function* () {
const count = yield* RefSubject.make(0);
return html`<button type="button" onclick=${RefSubject.update(count, (n) => n + 1)}>
Count: ${count}
</button>`;
});
const LabelledCounter = component(function* (label: string, initial: number) {
const count = yield* RefSubject.make(initial);
return html`<section aria-label=${label}>
<p>${label}: ${count}</p>
<button type="button" onclick=${RefSubject.update(count, (value) => value + 1)}>Add one</button>
</section>`;
});
const counters = html`${CounterNotice}${Counter}${LabelledCounter("Second counter", 4)}`;
CounterNotice needs no generator: html already describes its output. Counter allocates local state, so it uses component; its zero-argument generator produces an Fx value. LabelledCounter is a function; calling it with its arguments produces an Fx. Both are lazy descriptions: setup runs when their output is observed/rendered. Each independent execution of LabelledCounter owns its own count. Sharing the function does not share a singleton RefSubject.
The implementation distinguishes those forms using JavaScript body.length. Avoid a component signature whose only parameter has a default value or whose only parameter is a rest argument: those can have runtime length zero while appearing callable in TypeScript. Prefer one required options object and put defaults inside the generator. A component needing no arguments should be used as a value rather than called as a function.
Each execution owns a child Scope
Each execution forks the required parent Scope and provides that child to both the generator’s setup and its returned renderable. Scoped work started in setup and subscriptions in the template therefore share one instance lifetime. Completion, failure, or interruption closes the child; closing the parent closes every child. Siblings keep separate resources.
Returning a template does not end this lifetime at its first DOM emission: its subscription stays
active while mounted. Even a component returning a scalar requires a parent Scope. Use
Effect.forkScoped for ongoing work owned by the instance; provide longer-lived application
services outside the component when their work should survive it.
Result types and pipelines
The generator can return any supported renderable input, including a template, another component, or text. Format domain objects explicitly. Errors and service requirements from both setup and output remain in the resulting Fx; component does not catch failures or provide application services.
Optional pipeline callbacks receive the preceding output followed by the original component arguments, as in Fx.fn. They run when the component value is constructed or the component function is called. Put execution-time side effects inside Fx/Effect operators; the generator itself remains lazy. See the component API for generic inference and pipeline overloads.
Debug construction separately from rendering
An “expression is not callable” error usually means a zero-argument component is being called. A missing service is not solved by casting R to never; provide it where its lifetime is owned. Repeated initial state means the component is being re-executed or remounted rather than receiving updates through one existing RefSubject. A ref that never runs may simply mean no DOM renderer observed the output.
Continue with building UI components for a complete asynchronous save policy, Dom for host authoring, and Storybook for scoped mounting. API: Template component.