Defines a named operation that belongs to one mounted island. Use actions for form submissions, mutations, and other work that needs reactive pending, result, and error state.
When a shorthand island needs a reusable operation, expand ilha(() => JSX) into .action(...).render(...). The builder adds the action without changing the island model.
Basic usage
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 from "ilha";
const const Counter: Island<RootInput, MergeState<RootState, "count", number>>Counter = 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>.state<number, "count">(key: "count", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>state("count", 0)
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>.action<"increment", number, number>(key: "increment", fn: (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => number): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => number>>action("increment", (amount: numberamount: number, { state: IslandState<MergeState<RootState, "count", number>>state }) => {
state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count((count: numbercount) => count: numbercount + amount: numberamount);
return state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count();
})
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: number, ctx: ActionContext<...>) => number>>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"increment", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => number>>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state, action: IslandActions<RootActions & Record<"increment", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => number>>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<"increment", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => number>>action.increment: (props: number) => voidincrement(1)}>
Count: {state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}
</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));The first callback argument is the action payload. The second is the action context.
Call actions from events
Each action becomes a function on the render context’s action object:
action.increment(1);An action without a payload is callable with no arguments:
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 from "ilha";
const const Reset: Island<RootInput, MergeState<RootState, "count", number>>Reset = 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>.state<number, "count">(key: "count", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>state("count", 1)
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>.action<"reset", undefined, void>(key: "reset", fn: (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"reset", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action("reset", (_: undefined_, { state: IslandState<MergeState<RootState, "count", number>>state }) => state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count(0))
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"reset", (props: undefined, ctx: ActionContext<...>) => void>>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"reset", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ action: IslandActions<RootActions & Record<"reset", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, 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<"reset", (props: undefined, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action.reset: ActionAccessor<undefined, void>reset}>Reset</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));Use an action directly as a native event handler when its payload matches the event type:
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 from "ilha";
const const Form: Island<RootInput, RootState>Form = 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<"submit", SubmitEvent, Promise<any>>(key: "submit", fn: (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>action("submit", async (event: SubmitEventevent: SubmitEvent, { signal: AbortSignalsignal }) => {
event: SubmitEventevent.Event.preventDefault(): voidThe **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)preventDefault();
const const form: HTMLFormElementform = event: SubmitEventevent.Event.currentTarget: EventTarget | nullThe **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)currentTarget as HTMLFormElement;
const const response: Responseresponse = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch("/api/profile", {
RequestInit.method?: string | undefinedA string to set request's method.method: "POST",
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request's body.body: new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormDataThe **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch(), XMLHttpRequest.send() or navigator.sendBeacon() methods. It uses the same format a form would use if the encoding type were set to "multipart/form-data".
[MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)FormData(const form: HTMLFormElementform),
RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal,
});
if (!const response: Responseresponse.Response.ok: booleanThe **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok)ok) throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error("Save failed");
return const response: Responseresponse.Body.json(): Promise<any>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json();
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>action }) => (
<"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form onsubmit?: NativeEventHandler<SubmitEvent & {
readonly currentTarget: HTMLFormElement;
}> | undefined
onsubmit={action: IslandActions<RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>action.submit: ActionAccessor<SubmitEvent, Promise<any>>submit}>
<"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
input name?: string | RawHtml | undefinedname="name" />
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button disabled?: boolean | undefineddisabled={action: IslandActions<RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>action.submit: ActionAccessor<SubmitEvent, Promise<any>>submit.pending: booleanpending}>
{action: IslandActions<RootActions & Record<"submit", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<any>>>action.submit: ActionAccessor<SubmitEvent, Promise<any>>submit.pending: booleanpending ? "Saving…" : "Save"}
</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
</"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form>
));Transform event data
Keep DOM-specific conversion in the native handler when an action accepts an application payload:
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 from "ilha";
const const Newsletter: Island<RootInput, RootState>Newsletter = 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<"subscribe", string, Promise<void>>(key: "subscribe", fn: (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action("subscribe", async (email: stringemail: string, { signal: AbortSignalsignal }) => {
await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch("/api/newsletter", {
RequestInit.method?: string | undefinedA string to set request's method.method: "POST",
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request's body.body: var JSON: JSONAn intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.JSON.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)Converts a JavaScript value to a JavaScript Object Notation (JSON) string.stringify({ email: stringemail }),
RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal,
});
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action }) => (
<"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form
onsubmit?: NativeEventHandler<SubmitEvent & {
readonly currentTarget: HTMLFormElement;
}> | undefined
onsubmit={(event: SubmitEvent & {
readonly currentTarget: HTMLFormElement;
}
event) => {
event: SubmitEvent & {
readonly currentTarget: HTMLFormElement;
}
event.Event.preventDefault(): voidThe **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)preventDefault();
const const email: stringemail = var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.String(
new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormDataThe **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch(), XMLHttpRequest.send() or navigator.sendBeacon() methods. It uses the same format a form would use if the encoding type were set to "multipart/form-data".
[MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)FormData(event: SubmitEvent & {
readonly currentTarget: HTMLFormElement;
}
event.currentTarget: EventTarget & HTMLFormElementThe **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)currentTarget).FormData.get(name: string): FormDataEntryValue | nullThe **`get()`** method of the FormData interface returns the first value associated with a given key from within a FormData object. If you expect multiple values and want all of them, use the getAll() method instead.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)get("email") ?? "",
);
action: IslandActions<RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action.subscribe: (props: string) => voidsubscribe(const email: stringemail);
}}
>
<"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
input name?: string | RawHtml | undefinedname="email" type?: "number" | RawHtml | "button" | "search" | "time" | "image" | "text" | "reset" | "submit" | "hidden" | "email" | "checkbox" | "color" | "date" | "datetime-local" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="email" required?: boolean | undefinedrequired />
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button disabled?: boolean | undefineddisabled={action: IslandActions<RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action.subscribe: ActionAccessor<string, Promise<void>>subscribe.pending: booleanpending}>
{action: IslandActions<RootActions & Record<"subscribe", (props: string, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action.subscribe: ActionAccessor<string, Promise<void>>subscribe.pending: booleanpending ? "Joining…" : "Join"}
</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
</"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form>
));The native handler owns event behavior and payload conversion. The action owns the operation and its reactive state.
Reuse an action from multiple handlers
Call the same action from any rendered element:
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 from "ilha";
const const Quantity: Island<RootInput, MergeState<RootState, "count", number>>Quantity = 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>.state<number, "count">(key: "count", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>state("count", 1)
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions>.action<"change", number, void>(key: "change", fn: (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void): IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"change", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action("change", (amount: numberamount: number, { state: IslandState<MergeState<RootState, "count", number>>state }) => {
state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
(value: SignalSetter<number>) => void (+1 overload)
count((count: numbercount) => var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.max(...values: number[]): numberReturns the larger of a set of supplied numeric expressions.max(0, count: numbercount + amount: numberamount));
})
.IlhaBuilder<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"change", (props: number, ctx: ActionContext<...>) => void>>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "count", number>, RootDerived, RootActions & Record<"change", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>) => string | RawHtml): Island<RootInput, MergeState<RootState, "count", number>>render(({ state: IslandState<MergeState<RootState, "count", number>>state, action: IslandActions<RootActions & Record<"change", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action }) => (
<"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>
<"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<"change", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action.change: (props: number) => voidchange(-1)}>Remove</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
<"output": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLOutputElement> & {
for?: string;
form?: string;
name?: string;
}>
output>{state: IslandState<MergeState<RootState, "count", number>>state.count: MarkedSignalAccessor
() => number (+1 overload)
count()}</"output": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLOutputElement> & {
for?: string;
form?: string;
name?: string;
}>
output>
<"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<"change", (props: number, ctx: ActionContext<RootInput, MergeState<RootState, "count", number>, RootDerived>) => void>>action.change: (props: number) => voidchange(1)}>Add</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
</"div": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLDivElement>>div>
));Use .on() for advanced listeners
Prefer native JSX event props when one rendered element owns the event. Use .on() when you need a CSS selector, an island-host listener, the full island handler context, or multiple listener modifiers.
The first argument combines an optional selector and event name with @:
.on("button@click", handler) // descendant buttons
.on("@click", handler) // island host
.on("button@click:once:capture", handler) // combined modifiersDeclare actions before .on() to expose them through the handler context:
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 from "ilha";
const const Registration: Island<RootInput, RootState>Registration = 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<"registerUser", SubmitEvent, Promise<void>>(key: "registerUser", fn: (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action(
"registerUser",
async (event: SubmitEventevent: SubmitEvent, { signal: AbortSignalsignal }) => {
event: SubmitEventevent.Event.preventDefault(): voidThe **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)preventDefault();
const const form: HTMLFormElementform = event: SubmitEventevent.Event.currentTarget: EventTarget | nullThe **`currentTarget`** read-only property of the Event interface identifies the element to which the event handler has been attached.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget)currentTarget as HTMLFormElement;
await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch("/api/register", {
RequestInit.method?: string | undefinedA string to set request's method.method: "POST",
RequestInit.body?: BodyInit | null | undefinedA BodyInit object or null to set request's body.body: new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormDataThe **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch(), XMLHttpRequest.send() or navigator.sendBeacon() methods. It uses the same format a form would use if the encoding type were set to "multipart/form-data".
[MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)FormData(const form: HTMLFormElementform),
RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal,
});
},
)
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.on<"form@submit">(selectorOrCombined: "form@submit", handler: (ctx: HandlerContextFor<RootInput, RootState, "submit", RootDerived, RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>) => void | Promise<void>): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<...>>on("form@submit", ({ action: IslandActions<RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action, event: SubmitEventevent }) =>
action: IslandActions<RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>action.registerUser: (props: SubmitEvent) => voidregisterUser(event: SubmitEventevent),
)
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"registerUser", (props: SubmitEvent, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<void>>>) => string | RawHtml): Island<RootInput, RootState>render(() => (
<"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form>
<"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
input name?: string | RawHtml | undefinedname="email" type?: "number" | RawHtml | "submit" | "reset" | "button" | "search" | "time" | "image" | "text" | "hidden" | "email" | "checkbox" | "color" | "date" | "datetime-local" | "file" | "month" | "password" | "radio" | "range" | "tel" | "url" | "week" | undefinedtype="email" />
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>Register</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
</"form": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLFormElement> & FormAttributes>form>
));The .on() handler receives state, derived, action, input, host, target, event, and signal. Synchronous signal writes are batched, listeners clean up on unmount, and thrown errors route through .onError().
Reactive action state
Every action exposes three read-only reactive properties:
action.save.pending; // boolean
action.save.data; // returned value | undefined
action.save.error; // Error | undefinedReading these properties inside .render() or .effect() subscribes that scope. Ilha re-renders when the action state changes.
When asynchronous invocations overlap, pending stays true until all of them settle. Only the latest-started invocation can publish data or error, so an older response cannot replace a newer result.
Starting another invocation keeps the previous successful data available while pending. A successful invocation clears the previous error.
Action context
The callback receives an ActionContext as its second argument:
{
state: IslandState; // local state accessors
derived: IslandDerived; // current derived values
input: TInput; // resolved input props
host: Element; // mounted island host
signal: AbortSignal; // aborts when the island unmounts
}Pass signal to fetch and other abort-aware APIs. Ilha ignores action settlements after unmount.
Actions are also available as action inside .on(), .effect(), .onMount(), and .onError() callbacks when you declare the action earlier in the builder chain.
Errors
Ilha stores a thrown or rejected error in action.name.error. It also routes the error through the island error sink with source: "action":
- Local
.onError()handlers - Global
onUncaughtError()handlers console.error
An AbortError caused by unmount cancellation is expected and does not reach the error sink.
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 from "ilha";
const const Save: Island<RootInput, RootState>Save = 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<"save", undefined, Promise<never>>(key: "save", fn: (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>action("save", async () => {
throw new var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error("Could not save");
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.onError(fn: (ctx: ErrorContext<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>) => void): IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>onError(({ error: Errorerror, source: ErrorSourcesource }) => {
if (source: ErrorSourcesource === "action") var console: Consoleconsole.Console.error(...data: any[]): voidThe **`console.error()`** static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static)error(error: Errorerror.Error.message: stringmessage);
})
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<...>>>.render(fn: (ctx: RenderContext<RootInput, RootState, RootDerived, RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>) => string | RawHtml): Island<RootInput, RootState>render(({ action: IslandActions<RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>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<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>action.save: ActionAccessor<undefined, Promise<never>>save}>Save</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
{action: IslandActions<RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>action.save: ActionAccessor<undefined, Promise<never>>save.error: Error | undefinederror && <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{action: IslandActions<RootActions & Record<"save", (props: undefined, ctx: ActionContext<RootInput, RootState, RootDerived>) => Promise<never>>>action.save: ActionAccessor<undefined, Promise<never>>save.error: Errorerror.Error.message: stringmessage}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>}
</>
));SSR and hydration
Actions are client operations. During SSR, every action reports pending === false, with no data or error. Calling an action during SSR does nothing and logs a development warning.
Action state is transient and is not included in hydration snapshots. A hydrated island starts with idle action state.
Notes
- Action keys must be unique within one builder chain.
- The action function returns
void; read its result throughdatainstead of awaiting the call site. - Synchronous state writes inside an action are batched into one propagation pass.
- Use
@ilha/storeactions for mutations shared across multiple islands.