Skip to content

.action()

Define named synchronous or asynchronous island operations with reactive pending, data, and error state.

Updated View as Markdown

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.

Interactive Tutorial

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(): void
The **`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 | null
The **`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 | undefined
A string to set request's method.
method
: "POST",
RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
body
: new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormData
The **`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 | undefined
An AbortSignal to set request's signal.
signal
,
}); if (!const response: Responseresponse.Response.ok: boolean
The **`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 | undefined
A string to set request's method.
method
: "POST",
RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
body
: var JSON: JSON
An 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.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
({ email: stringemail }),
RequestInit.signal?: AbortSignal | null | undefined
An 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(): void
The **`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) => FormData
The **`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 & HTMLFormElement
The **`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 | null
The **`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: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.max(...values: number[]): number
Returns the larger of a set of supplied numeric expressions.
@paramvalues Numeric expressions to be evaluated.
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 modifiers

Declare 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(): void
The **`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 | null
The **`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 | undefined
A string to set request's method.
method
: "POST",
RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
body
: new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormData
The **`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 | undefined
An 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 | undefined

Reading 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":

  1. Local .onError() handlers
  2. Global onUncaughtError() handlers
  3. 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[]): void
The **`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 through data instead of awaiting the call site.
  • Synchronous state writes inside an action are batched into one propagation pass.
  • Use @ilha/store actions for mutations shared across multiple islands.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close