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.

Interactive Tutorial

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: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
Document.title: string

The document.title property gets or sets the current title of the document. When present, it defaults to the value of the .

MDN Reference

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[]): void

The console.log() static method outputs a message to the console.

MDN Reference

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: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
Document.title: string

The document.title property gets or sets the current title of the document. When present, it defaults to the value of the .

MDN Reference

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: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
Document.body: HTMLElement

The Document.body property represents the or node of the current document, or null if no such element exists.

MDN Reference

body
.
ElementCSSInlineStyle.style: CSSStyleDeclaration
style
.
CSSStyleProperties.backgroundColor: string

The background-color CSS property sets the background color of an element.

MDN Reference

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[]): void

The console.log() static method outputs a message to the console.

MDN Reference

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-runsYes, when dependencies changeNo, runs once
Tracks signalsYesNo
Receives derivedNoYes
Cleanup supportYesYes
Use forReactive sync to external APIsOne-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.