#Effect
Registers a reactive side effect that runs after the island mounts and re-runs automatically whenever any signal it reads changes. Use it to sync state to the outside world — the DOM, browser APIs, timers, or external systems.
#Basic usage
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<string, never>, "title", string>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<string, "title">(key: "title", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>> state ("title", "Hello")
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, MergeState<Record<string, never>, "title", string>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>> effect (({ state: IslandState<MergeState<Record<string, never>, "title", string>> state }) => {
var document: Documentwindow.document returns a reference to the document contained in the window.
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 = state: IslandState<MergeState<Record<string, never>, "title", string>> state .title: MarkedSignalAccessor
() => string (+1 overload)
title ();
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "title", string>> render (({ state: IslandState<MergeState<Record<string, never>, "title", string>> state }) => `<input value="${state: IslandState<MergeState<Record<string, never>, "title", string>> state .title: MarkedSignalAccessor
() => string (+1 overload)
title ()}" />`);Every time state.title changes, the effect re-runs and updates document.title.
#Cleanup
Return a function from the effect to clean up before the next run or on unmount:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<number, "delay">(key: "delay", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>, Record<string, never>> state ("delay", 1000)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>, Record<string, never>> effect (({ state: IslandState<MergeState<Record<string, never>, "delay", number>> state }) => {
const const id: number id = function setInterval(handler: TimerHandler, timeout?: number, ...arguments: any[]): number setInterval (() => {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log ("tick");
}, state: IslandState<MergeState<Record<string, never>, "delay", number>> state .delay: MarkedSignalAccessor
() => number (+1 overload)
delay ());
return () => function clearInterval(id: number | undefined): void clearInterval (const id: number id );
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "delay", number>> render (({ state: IslandState<MergeState<Record<string, never>, "delay", number>> state }) => `<p>Interval: ${state: IslandState<MergeState<Record<string, never>, "delay", number>> state .delay: MarkedSignalAccessor
() => number (+1 overload)
delay ()}ms</p>`);The cleanup runs before the effect re-runs with new values, and once more on unmount. This prevents stale timers, subscriptions, or event listeners from accumulating.
#Effect context
The effect function receives an EffectContext:
{
state: IslandState; // reactive state signals
input: TInput; // resolved input props
host: Element; // island root element
}Note that derived is not available in effects. If you need a derived value inside an effect, read the state signals it depends on directly and let the effect track those dependencies.
#Multiple effects
Chain .effect() as many times as needed. Each runs independently with its own dependency tracking:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<string, "title">(key: "title", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>> state ("title", "Hello")
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "title", string>, Record<string, never>>.state<string, "color">(key: "color", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>> state ("color", "teal")
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>> effect (({ state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state }) => {
var document: Documentwindow.document returns a reference to the document contained in the window.
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 = state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state .title: MarkedSignalAccessor
() => string (+1 overload)
title ();
})
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>> effect (({ state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state }) => {
var document: Documentwindow.document returns a reference to the document contained in the window.
document .Document.body: HTMLElementThe Document.body property represents the or node of the current document, or null if no such element exists.
body .ElementCSSInlineStyle.style: CSSStyleDeclaration style .CSSStyleProperties.backgroundColor: stringThe background-color CSS property sets the background color of an element.
backgroundColor = state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state .color: MarkedSignalAccessor
() => string (+1 overload)
color ();
})
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> render (({ state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state }) => `<p>${state: IslandState<MergeState<MergeState<Record<string, never>, "title", string>, "color", string>> state .title: MarkedSignalAccessor
() => string (+1 overload)
title ()}</p>`);#Conditional reads
Dependencies are tracked based on which signals are actually read during a run. Signals inside a branch that does not execute are not tracked:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<boolean, "enabled">(key: "enabled", init?: StateInit<Record<string, unknown>, boolean> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "enabled", boolean>, Record<string, never>> state ("enabled", false)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "enabled", boolean>, Record<string, never>>.state<number, "value">(key: "value", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>, Record<string, never>> state ("value", 0)
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>, Record<string, never>>.effect(fn: (ctx: EffectContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>>) => (() => void) | void): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>, Record<string, never>> effect (({ state: IslandState<MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> state }) => {
if (!state: IslandState<MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> state .enabled: MarkedSignalAccessor
() => boolean (+1 overload)
enabled ()) return; // if false, state.value is never read
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (state: IslandState<MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> state .value: MarkedSignalAccessor
() => number (+1 overload)
value ()); // only tracked when enabled is true
})
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>, Record<string, never>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>, Record<string, never>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> render (({ state: IslandState<MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> state }) => `<p>${state: IslandState<MergeState<MergeState<Record<string, never>, "enabled", boolean>, "value", number>> state .value: MarkedSignalAccessor
() => number (+1 overload)
value ()}</p>`);This means the effect only re-runs when state.value changes if state.enabled was true during the last run.
#.effect() vs .onMount()
Both run after mount, but they serve different purposes:
.effect() | .onMount() | |
|---|---|---|
| Re-runs | Yes, when dependencies change | No, runs once |
| Tracks signals | Yes | No |
Receives derived | No | Yes |
| Cleanup support | Yes | Yes |
| Use for | Reactive sync to external APIs | One-time setup |
If you need something to happen only once after mount, use .onMount(). If you need it to stay in sync with state over time, use .effect().
#Notes
- Effects run client-side only. They are not called during SSR.
- The effect runs synchronously after the first mount, before the browser paints. Keep effects fast to avoid blocking rendering.
- Avoid writing to signals inside an effect that reads those same signals — this creates an infinite loop.