A component is a class with a render() method that returns an Element. It combines a store (reactive state) with element construction (JSX). The simplest possible component:
Write a component that owns its state
A typical component owns its state and produces elements from it. @reactive turns class fields into signals; JSX reads them as live bindings.
Share state across components
When state needs to be shared across components, move it into a standalone store β a class with @reactive fields and no render(). Components read from the store; the store holds no reference to components.
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks β
Equivalent to manually creating a private signal and getter/setter:
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
A decorator that makes a class field reactive by automatically wrapping its value in a signal.
The field behaves like a normal property (get/set) but reactivity is tracked under the hood.
Any reads will subscribe to the signal and any writes will trigger updates.
@example
classCounter {
\@reactive() count:number=0;
}
constcounter=newCounter();
counter.count++; // Triggers reactivity
console.log(counter.count); // Subscribes to changes
@remarks β
Equivalent to manually creating a private signal and getter/setter:
classCounter {
#count=signal(0);
getcount() { returnthis.#count(); }
setcount(value) { this.#count(value); }
}
reactive()
CounterStore.count: number
count=0;
CounterStore.doubled: () => number
doubled=
computed<number>(getter: (previousValue?:number|undefined) => number): () => number
Creates a lazily-evaluated computed value.
The getter is only called when the computed value is read and one of
its dependencies has changed since the last evaluation. If nothing has
changed the cached value is returned without re-running getter.
Computed values are read-only; they cannot be set directly.
@param β getter - Pure function deriving a value from other reactive sources.
Receives the previous value as an optional optimisation hint.
The same counter instance can also drive a React component or a custom element β see Stores and React integration.
Rendering lists
For keyed list rendering, use the For component β it reconciles a reactive array into the DOM without re-rendering stable rows. See For for the full API.
Function components
A function component is a plain function that returns an Element. Props arrive exactly as the caller wrote them β the runtime transforms nothing. A prop passed as a signal arrives as that signal; a prop passed as a value arrives as that value.
That makes the declared type the contract on both sides. Declare plain types when a component takes static props only β callers then cannot pass a signal:
Declare Props<P> to accept either form on every key. Hand a prop straight to JSX, which subscribes when it is reactive, or read it with resolve:
import {
functionresolve<T>(value:MaybeReactive<T>):T
Resolve a
MaybeReactive
to its current value. Calls the getter when
reactive (a signal or computed); returns the value as-is otherwise β an
unbranded function is a value, not a source, so a callback survives intact.
This is how a function component reads a prop it declared MaybeReactive:
the runtime hands props over exactly as the caller wrote them, so the value
may be either form. Reading inside an effect or a JSX getter subscribes.
@example
resolve(5); // 5
resolve(count); // current count value β signal
resolve(props.label); // current value, whichever form the caller passed
resolve(() =>5); // the function itself β unbranded, so not a source
Props of a function component that accepts reactive values β the same type
as
MaybeReactiveProps
, under the name components reach for. Each key
holds a plain value or a reactive source; the runtime hands the prop over
exactly as the caller wrote it.
Read a key with resolve(props.x), or pass it straight into JSX, which
accepts either form. Omit it and declare the plain value type when a
component takes static props only β callers then cannot pass a signal.
Props of a function component that accepts reactive values β the same type
as
MaybeReactiveProps
, under the name components reach for. Each key
holds a plain value or a reactive source; the runtime hands the prop over
exactly as the caller wrote it.
Read a key with resolve(props.x), or pass it straight into JSX, which
accepts either form. Omit it and declare the plain value type when a
component takes static props only β callers then cannot pass a signal.
to its current value. Calls the getter when
reactive (a signal or computed); returns the value as-is otherwise β an
unbranded function is a value, not a source, so a callback survives intact.
This is how a function component reads a prop it declared MaybeReactive:
the runtime hands props over exactly as the caller wrote them, so the value
may be either form. Reading inside an effect or a JSX getter subscribes.
@example
resolve(5); // 5
resolve(count); // current count value β signal
resolve(props.label); // current value, whichever form the caller passed
resolve(() =>5); // the function itself β unbranded, so not a source
Props<P> is an alias for MaybeReactiveProps<P> β each key is T | Computed<T>. Use MaybeReactive<T> on individual keys when only some props should accept a signal.
Because nothing is wrapped, an omitted optional prop is plain undefined, so ?? defaults work as written:
When a body would rather read one uniform shape than branch on which form arrived, convert the props with computedProps. Every key becomes a getter β including keys the caller omitted β so reads never need resolve:
Turn props into a bag of per-key getters, so a body reads one shape no matter
which form the caller passed. Opt-in: the JSX runtime hands function
components their props untouched.
Every key is callable, including one the caller omitted. A getter is always
truthy, so defaults go on the call: props.excited() ?? "β¦". Keys are
branded sources, so they keep working when forwarded to a child component.
Function props are the limit: a prop taking arguments is rejected, and a
zero-arg one types as its return value. Read those off the raw props.
Props of a function component that accepts reactive values β the same type
as
MaybeReactiveProps
, under the name components reach for. Each key
holds a plain value or a reactive source; the runtime hands the prop over
exactly as the caller wrote it.
Read a key with resolve(props.x), or pass it straight into JSX, which
accepts either form. Omit it and declare the plain value type when a
component takes static props only β callers then cannot pass a signal.
Props of a function component that accepts reactive values β the same type
as
MaybeReactiveProps
, under the name components reach for. Each key
holds a plain value or a reactive source; the runtime hands the prop over
exactly as the caller wrote it.
Read a key with resolve(props.x), or pass it straight into JSX, which
accepts either form. Omit it and declare the plain value type when a
component takes static props only β callers then cannot pass a signal.
Turn props into a bag of per-key getters, so a body reads one shape no matter
which form the caller passed. Opt-in: the JSX runtime hands function
components their props untouched.
Every key is callable, including one the caller omitted. A getter is always
truthy, so defaults go on the call: props.excited() ?? "β¦". Keys are
branded sources, so they keep working when forwarded to a child component.
Function props are the limit: a prop taking arguments is rejected, and a
zero-arg one types as its return value. Read those off the raw props.
Note where the default sits. A getter is always truthy, so props.placeholder ?? "β¦" would never fall back β inside a bag the default goes on the call: props.placeholder() ?? "β¦".
Every key is a reactive source, whether the caller passed a signal or a plain value. That means a getter keeps working when you forward it to a child component β the child can call it, resolve it, or convert its own props with computedProps without wrapping it twice.
Function props
computedProps infers its shape from the argument, and a callable cannot be told apart from a getter β Computed<T>is() => T. So a prop that takes arguments (a render prop, a handler with parameters) is rejected rather than mistyped:
// β computedProps: a prop that takes arguments cannot be inferred here
A zero-arg function prop cannot be rejected β Signal<T> and Computed<T> are zero-arg callables themselves, so banning them would ban reactive props. It types as its return value while the runtime hands back the function, so read those raw too.
Reach for computedProps when a component has several props it reads repeatedly. For one or two props, raw reads and resolve are less machinery.