Reactive signals are the primitive that powers state in ilha. In addition to .state() (local to an island), ilha exports several helpers for cross-island sharing, derived values, side effects, performance, and control:
| Helper | Purpose |
|---|---|
signal() |
Create a free-standing signal for one-off shared state |
computed() |
Create a free-standing read-only value derived from signals |
context() |
Create a named global signal accessible from anywhere by key |
effect() |
Run a reactive side effect outside any island |
batch() |
Group multiple writes into a single propagation pass |
untrack() |
Read a signal without subscribing the surrounding scope |
signal(initial)
Creates a free-standing reactive signal that lives outside any island. Useful for sharing state across multiple islands without prop drilling, or for binding form inputs to module-level state.
Basic usage
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal } from "ilha";
const const count: SignalAccessor<number>count = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const count: MarkedSignalAccessor
() => number (+1 overload)
count(); // → 0 (read)
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count(5); // → sets to 5 (write)
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count((previous: numberprevious) => previous: numberprevious + 1); // → updates from the latest valueThe updater form also works with context() and nested .select() accessors. If a signal stores a function, return the replacement function from an updater: callback(() => nextCallback).
Reading the signal inside any reactive scope — .render(), .derived(), .effect() — automatically subscribes that scope, so when the signal changes, dependents re-run as if it were local state.
Sharing state between islands
Because signal() returns a plain accessor, you can import it into any island. When one island writes to it, all others that read it re-render automatically:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal } from "ilha";
const const cartCount: SignalAccessor<number>cartCount = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const const CartButton: Island<RootInput, RootState>CartButton = const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.action<"addToCart", undefined, void>(key: "addToCart", fn: (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action("addToCart", () => const cartCount: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
cartCount((count: numbercount) => count: numbercount + 1))
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action }) => (
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button onclick?: NativeEventHandler<PointerEvent & {
readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick={action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action.addToCart: ActionAccessor<undefined, void>addToCart}>Add to cart</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));
const const CartBadge: Island<RootInput, RootState>CartBadge = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{const cartCount: MarkedSignalAccessor
() => number (+1 overload)
cartCount()}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>); Both islands share the same cartCount signal. Clicking the button in CartButton updates the badge in CartBadge without any wiring between them.
Using signals in bind: bindings
Pass a signal directly into a bind: attribute to sync a form element with module-level state:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal } from "ilha";
const const query: SignalAccessor<string>query = signal<string>(initial: string): SignalAccessor<string>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal("");
const const SearchInput: Island<RootInput, RootState>SearchInput = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(
() => <"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
input type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "checkbox" | "color" | "date" | "datetime-local" | "email" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="search" "bind:value"?: BindAccessor<string | number> | undefinedbind:"bind:value"?: BindAccessor<string | number> | undefinedvalue={const query: SignalAccessor<string>query} />,
);
const const SearchResults: Island<RootInput, RootState>SearchResults = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Results for: {const query: MarkedSignalAccessor
() => string (+1 overload)
query()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);When the user types, query updates and SearchResults re-renders automatically — no wiring between islands needed.
computed(fn)
Creates a free-standing, read-only value derived from other signals. fn is lazy and cached — it re-runs only when a signal it read has changed and the computed is read again.
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function computed<T>(fn: () => T): SignalAccessor<T>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed } from "ilha";
const const items: SignalAccessor<number[]>items = signal<number[]>(initial: number[]): SignalAccessor<number[]>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal([1, 2, 3]);
const const total: SignalAccessor<number>total = computed<number>(fn: () => number): SignalAccessor<number>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed(() =>
const items: MarkedSignalAccessor
() => number[] (+1 overload)
items().Array<number>.reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number (+2 overloads)Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.reduce((a: numbera, b: numberb) => a: numbera + b: numberb, 0),
);
const total: MarkedSignalAccessor
() => number (+1 overload)
total(); // → 6 (runs fn once, caches the result)
const total: MarkedSignalAccessor
() => number (+1 overload)
total(); // → 6 (cached, fn does not re-run)
const items: MarkedSignalAccessor
(value: SignalSetter<number[]>) => void (+1 overload)
items([1, 2, 3, 4]);
const total: MarkedSignalAccessor
() => number (+1 overload)
total(); // → 10 (a dependency changed, fn re-runs)Reading a computed inside any reactive scope — .render(), .derived(), .effect(), or top-level effect() — subscribes that scope, so dependents re-run when the computed’s resolved value changes:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function computed<T>(fn: () => T): SignalAccessor<T>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed } from "ilha";
const const base: SignalAccessor<number>base = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(1);
const const doubled: SignalAccessor<number>doubled = computed<number>(fn: () => number): SignalAccessor<number>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed(() => const base: MarkedSignalAccessor
() => number (+1 overload)
base() * 2);
const const Island: Island<RootInput, RootState>Island = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{const doubled: MarkedSignalAccessor
() => number (+1 overload)
doubled()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);Read-only
Writing to a computed is ignored and logs a dev warning — use signal() if you need a writable value:
import { function computed<T>(fn: () => T): SignalAccessor<T>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed } from "ilha";
const const c: SignalAccessor<number>c = computed<number>(fn: () => number): SignalAccessor<number>Create a free-standing read-only reactive value derived from other signals.
The computation is lazy and cached: `fn` re-runs only when a signal it read
changed and the computed is read again. Reading it inside a `.derived()`,
`.effect()`, `.render()`, or top-level `effect()` subscribes that scope —
dependents re-run when the computed's value changes.
```ts
const items = ilha.signal([1, 2, 3]);
const total = ilha.computed(() => items().reduce((a, b) => a + b, 0));
```computed(() => 1);
(const c: SignalAccessor<number>c as (v: numberv: number) => void)(99); // ignored, dev warning logged
const c: MarkedSignalAccessor
() => number (+1 overload)
c(); // → 1context(key, initial)
Creates a named global signal — a reactive signal shared across all islands. Identical keys always return the same signal instance, which makes it useful for app-wide singletons (theme, locale, current user) where you want registry semantics.
import { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const const theme: ContextSignal<string>theme = context<string>(key: string, initial: string): ContextSignal<string>context("app.theme", "light");
const theme: () => string (+1 overload)theme(); // → "light"
const theme: (value: SignalSetter<string>) => void (+1 overload)theme("dark"); // → sets to "dark"signal() vs context()
Both return the same accessor shape and can be used with bind: template syntax. Reach for signal() when you hold the reference yourself and import it where needed. Reach for context() when you want a name-keyed registry so the same signal can be looked up from anywhere by string key — for example, when the consumer lives in a different package or module from where the signal is defined.
Sharing state between islands
Any island that calls context() with the same key gets the same signal. When one island writes to it, all others that read it re-render automatically:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const const cartCount: ContextSignal<number>cartCount = context<number>(key: string, initial: number): ContextSignal<number>context("cart.count", 0);
const const CartButton: Island<RootInput, RootState>CartButton = const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.action<"addToCart", undefined, void>(key: "addToCart", fn: (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action("addToCart", () => const cartCount: (value: SignalSetter<number>) => void (+1 overload)cartCount((count: numbercount) => count: numbercount + 1))
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action }) => (
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button onclick?: NativeEventHandler<PointerEvent & {
readonly currentTarget: HTMLButtonElement;
}> | undefined
onclick={action: IslandActions<RootActions & Record<"addToCart", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => void>>action.addToCart: ActionAccessor<undefined, void>addToCart}>Add to cart</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));
const const CartBadge: Island<RootInput, RootState>CartBadge = ilha<RootInput>(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>ilha(() => <"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>{const cartCount: () => number (+1 overload)cartCount()}</"span": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLSpanElement>>span>); Using context in bind: bindings
Pass a context signal directly into a bind: attribute to sync a form element across islands:
import ilha, { context } from "ilha";
const query = context("search.query", "");
const SearchInput = ilha(
() => <input type="search" bind:value={query} />,
);
const SearchResults = ilha(() => <p>Results for: {query()}</p>);Initializing with a type
The second argument sets the initial value and infers the signal type. The type is fixed at first call — subsequent calls with the same key return the existing signal regardless of what initial value is passed:
import { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const const count: ContextSignal<number>count = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0); // creates signal<number>
const const same: ContextSignal<number>same = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 999); // returns same signal, ignores 999This means context initialization is effectively first-write-wins. Define context signals in a shared module to ensure consistent initialization across your app:
// contexts.ts
import { context } from "ilha";
export const theme = context("app.theme", "light");
export const userId = context(
"app.userId",
null as string | null,
);
export const sidebar = context("ui.sidebar", true);Reading context inside effects and derived
Context signals are reactive — reading them inside .effect() or .derived() creates a dependency just like reading local state:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const const theme: ContextSignal<string>theme = context<string>(key: string, initial: string): ContextSignal<string>context("app.theme", "light");
const const Island: Island<RootInput, RootState>Island = const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.effect(fn: (ctx: EffectContext<RootInput, RootState, RootDerived, RootActions>) => (() => void) | void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>effect(() => {
var document: Document**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.Document.documentElement: HTMLElementThe **`documentElement`** read-only property of the Document interface returns the Element that is the root element of the document (for example, the <html> element for HTML documents).
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/documentElement)documentElement.HTMLOrSVGElement.dataset: DOMStringMap[MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/dataset)dataset["theme"] = const theme: () => string (+1 overload)theme();
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>render(() => <"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>content</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>);Whenever theme is updated anywhere in the app, this effect re-runs.
SSR behavior
context() is safe to call during SSR. The registry is module-level, so signals persist for the lifetime of the process. In a server environment where requests share the same module instance, be careful not to store user-specific state in context signals — use .input() and .state() for per-request data instead.
Releasing entries — context.delete() and context.clear()
The registry is otherwise append-only. Long-lived SPAs that mint dynamic keys, HMR cycles, and tests all need a way to release entries:
import { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context.function delete(key: string): booleanRemove a context signal from the registry. Returns true if it existed.delete("cart.count"); // → true if it existed, false otherwise
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context.function clear(): voidRemove all context signals from the registry (e.g. between tests).clear(); // remove every context signalDeleting a key does not affect accessors already handed out — they keep working against their original signal. It only means the next context(key, initial) call creates a fresh signal instead of returning the old one.
import { const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context } from "ilha";
const const first: ContextSignal<number>first = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0);
const first: (value: SignalSetter<number>) => void (+1 overload)first(5);
const context: (<T>(key: string, initial: T) => ContextSignal<T>) & {
delete(key: string): boolean;
clear(): void;
}
context.function delete(key: string): booleanRemove a context signal from the registry. Returns true if it existed.delete("ui.count");
const const second: ContextSignal<number>second = context<number>(key: string, initial: number): ContextSignal<number>context("ui.count", 0); // fresh signal, starts at 0
const first: () => number (+1 overload)first(); // → 5, unaffected by the delete
const second: () => number (+1 overload)second(); // → 0effect(fn)
Runs a free-standing reactive effect outside any island. fn runs once immediately and re-runs whenever a signal it read changes.
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect } from "ilha";
const const cart: SignalAccessor<{
count: number;
}>
cart = signal<{
count: number;
}>(initial: {
count: number;
}): SignalAccessor<{
count: number;
}>
Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal({ count: numbercount: 0 });
const const stop: () => voidstop = function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect(() => {
var document: Document**`window.document`** returns a reference to the document contained in the window.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)document.Document.title: stringThe **`document.title`** property gets or sets the current title of the document. When present, it defaults to the value of the <title>.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/title)title = `${const cart: MarkedSignalAccessor
() => {
count: number;
} (+1 overload)
cart().count: numbercount} items`;
});Import effect from "ilha" as a named export — import { effect } from "ilha". It is deliberately not on the default export: ilha.effect() is the island builder method that registers a per-island effect. The top-level effect() documented here is a standalone helper for code that runs outside any island.
Cleanup and stopping
fn may return a cleanup function, invoked before each re-run and once more when the effect is stopped:
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect } from "ilha";
const const delay: SignalAccessor<number>delay = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(1000);
const const stop: () => voidstop = function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect(() => {
const const id: numberid = function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval)setInterval(() => var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log("tick"), const delay: MarkedSignalAccessor
() => number (+1 overload)
delay());
return () => function clearInterval(id: number | undefined): void[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval)clearInterval(const id: numberid);
});
const stop: () => voidstop(); // disposes the effect and runs the final cleanupImplicit batching
Multiple synchronous signal writes inside the effect body produce a single propagation pass, the same as inside .on() handlers or .effect():
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect } from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
function effect(fn: () => void | (() => void)): () => voidRun a free-standing reactive effect outside any island. `fn` runs once
immediately and again whenever a signal it read changes. It may return a
cleanup function, invoked before each re-run and on stop. Signal writes
inside the effect are batched. Returns a stop function that disposes the
effect and runs the final cleanup.
```ts
const stop = effect(() => {
document.title = `${cart.count()} items`;
});
```effect(() => {
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a(const a: MarkedSignalAccessor
() => number (+1 overload)
a() + 1);
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b(const b: MarkedSignalAccessor
() => number (+1 overload)
b() + 1); // both writes flush together
});batch(fn)
Runs fn as an atomic batch — multiple signal writes inside the callback produce a single propagation pass, so dependents (effects, deriveds, island re-renders) see the final state and run once instead of once per write. Returns whatever fn returns.
Before and after
Without batch, each write triggers its own propagation pass:
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal } from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a(1); // → effects re-run
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b(2); // → effects re-run againWith batch, both writes flush together:
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function batch<T>(fn: () => T): TRun `fn` as an atomic batch — multiple signal writes inside the callback
produce a single propagation pass, so dependents (effects, deriveds,
island re-renders) see the final state and run once instead of once per
write. Returns whatever `fn` returns.
Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
you only need this when triggering multiple writes from outside an
island (e.g. from a top-level event listener or async callback).batch } from "ilha";
const const a: SignalAccessor<number>a = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const const b: SignalAccessor<number>b = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
batch<void>(fn: () => void): voidRun `fn` as an atomic batch — multiple signal writes inside the callback
produce a single propagation pass, so dependents (effects, deriveds,
island re-renders) see the final state and run once instead of once per
write. Returns whatever `fn` returns.
Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
you only need this when triggering multiple writes from outside an
island (e.g. from a top-level event listener or async callback).batch(() => {
const a: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
a(10);
const b: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
b(20);
}); // → effects re-run onceImplicit batching
.on() handlers and .effect() runs are batched implicitly, so you only need batch() when triggering multiple writes from outside an island — for example from a top-level event listener, a setTimeout callback, or a WebSocket message handler.
Nesting
Nested batch() calls are safe and only flush when the outermost batch ends:
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function batch<T>(fn: () => T): TRun `fn` as an atomic batch — multiple signal writes inside the callback
produce a single propagation pass, so dependents (effects, deriveds,
island re-renders) see the final state and run once instead of once per
write. Returns whatever `fn` returns.
Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
you only need this when triggering multiple writes from outside an
island (e.g. from a top-level event listener or async callback).batch } from "ilha";
const const count: SignalAccessor<number>count = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
batch<void>(fn: () => void): voidRun `fn` as an atomic batch — multiple signal writes inside the callback
produce a single propagation pass, so dependents (effects, deriveds,
island re-renders) see the final state and run once instead of once per
write. Returns whatever `fn` returns.
Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
you only need this when triggering multiple writes from outside an
island (e.g. from a top-level event listener or async callback).batch(() => {
batch<void>(fn: () => void): voidRun `fn` as an atomic batch — multiple signal writes inside the callback
produce a single propagation pass, so dependents (effects, deriveds,
island re-renders) see the final state and run once instead of once per
write. Returns whatever `fn` returns.
Note: `.on()` handlers and `.effect()` runs are batched implicitly, so
you only need this when triggering multiple writes from outside an
island (e.g. from a top-level event listener or async callback).batch(() => {
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count(1);
}); // still inside outer batch — no flush yet
const count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count(2);
}); // outermost batch ends — single flushuntrack(fn)
Runs fn with reactive tracking suspended. Reading signals inside fn returns their current value without subscribing the surrounding scope. Use this in effects or deriveds when you want to peek at state without causing a re-run on its changes.
React to A, peek at B
The canonical pattern: an effect should re-run when tracked changes, but read peeked only as a one-off value:
import const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha, { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function untrack<T>(fn: () => T): TRun `fn` with reactive tracking suspended. Reading signals inside `fn`
returns their current value without subscribing the surrounding scope.
Use this in effects/deriveds when you want to peek at state without
causing a re-run on its changes.untrack } from "ilha";
const const tracked: SignalAccessor<number>tracked = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(0);
const const peeked: SignalAccessor<string>peeked = signal<string>(initial: string): SignalAccessor<string>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal("hello");
const const Island: Island<RootInput, RootState>Island = const ilha: RootBuilder & DirectIslandFactory & {
html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
raw: (value: string) => RawHtml;
mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.effect(fn: (ctx: EffectContext<RootInput, RootState, RootDerived, RootActions>) => (() => void) | void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions>effect(() => {
// Re-runs when `tracked` changes, but NOT when `peeked` changes.
var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log(
const tracked: MarkedSignalAccessor
() => number (+1 overload)
tracked(),
untrack<string>(fn: () => string): stringRun `fn` with reactive tracking suspended. Reading signals inside `fn`
returns their current value without subscribing the surrounding scope.
Use this in effects/deriveds when you want to peek at state without
causing a re-run on its changes.untrack(() => const peeked: MarkedSignalAccessor
() => string (+1 overload)
peeked()),
);
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, RootState>render(() => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>x</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);untrack() returns whatever fn returns, so it also works for peeking at derived values or any other reactive read:
import { function signal<T>(initial: T): SignalAccessor<T>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal, function untrack<T>(fn: () => T): TRun `fn` with reactive tracking suspended. Reading signals inside `fn`
returns their current value without subscribing the surrounding scope.
Use this in effects/deriveds when you want to peek at state without
causing a re-run on its changes.untrack } from "ilha";
const const s: SignalAccessor<number>s = signal<number>(initial: number): SignalAccessor<number>Create a free-standing reactive signal that lives outside any island.
Useful for sharing state across islands without prop drilling, or for
binding form inputs to module-level state via the `bind:value=${signal}`
template syntax.
The returned accessor is a getter when called with no arguments and a
setter when called with one. Reading it inside a `.derived()`, `.effect()`,
or `.render()` automatically subscribes the surrounding reactive scope —
so when the signal changes, dependents re-run as if it were local state.signal(42);
const const value: numbervalue = untrack<number>(fn: () => number): numberRun `fn` with reactive tracking suspended. Reading signals inside `fn`
returns their current value without subscribing the surrounding scope.
Use this in effects/deriveds when you want to peek at state without
causing a re-run on its changes.untrack(() => const s: MarkedSignalAccessor
() => number (+1 overload)
s()); // → 42, no subscription createdNotes
signal()vscontext()— both return the same accessor shape and can be used withbind:template syntax. Usesignal()for one-off shared state where you hold the reference; usecontext()when you want a name-keyed registry.- Keys are global strings. Use namespaced keys like
"app.theme"or"cart.count"to avoid accidental collisions across different parts of your app. - Use
context.delete(key)orcontext.clear()to release entries — useful in HMR, tests, or long-lived SPAs that mint dynamic keys. See Releasing entries. - Context signals are not included in
.hydratable()snapshots. If you need server-rendered context values on the client, pass them as island props via.input()and initialize the context signal inside.onMount(). computed()values are read-only; they cannot be used asbind:targets. Usesignal()for writable bindings.- Top-level
effect()(imported from"ilha") is distinct from the.effect()builder method — seeeffect(fn)above.