Handle errors
Route uncaught errors from actions, effects, and events to per-island onError() handlers or a global onUncaughtError() sink.
Register a per-island error handler with onError(). The runtime routes uncaught errors through a central sink: local onError() handlers first (in declaration order), then the app-wide onUncaughtError() sink if the island has none, then console.error so nothing is swallowed silently.
import { function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError, const ilha: IlhaFactoryilha, function action<P, R>(fn: (payload: P, ctx: {
signal: AbortSignal;
}) => R): ActionAccessor<P, R>
Declare a reactive operation at this call position. Use plain functions
for ordinary operations; action() adds pending/data/error tracking,
concurrent-invocation bookkeeping, and lifecycle cancellation.action } from "ilha";
const const report: (error: unknown, source: string) => voidreport = (error: unknownerror: unknown, source: stringsource: string) => {
void error: unknownerror;
void source: stringsource;
};
const const Form: Island<unknown>Form = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => {
const const save: ActionAccessor<unknown, Promise<void>>save = action<unknown, Promise<void>>(fn: (payload: unknown, ctx: {
signal: AbortSignal;
}) => Promise<void>): ActionAccessor<unknown, Promise<void>>
Declare a reactive operation at this call position. Use plain functions
for ordinary operations; action() adds pending/data/error tracking,
concurrent-invocation bookkeeping, and lifecycle cancellation.action(async () => {
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",
});
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");
});
function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError(({ error: Errorerror, source: ErrorSourcesource }) => {
const report: (error: unknown, source: string) => voidreport(error: Errorerror, source: ErrorSourcesource);
});
return <"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={const save: ActionAccessor<unknown, Promise<void>>save}>Save</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>;
});
The context is { error, source, host }. Access state, derived values, actions, and props through lexical closure rather than copying them into the error context.
Error sources
Errors come from several places, and source tells you which:
| Source | Origin |
|---|---|
action |
An action() callback threw or rejected |
effect |
An effect() body or its cleanup threw |
once |
An effect.once() callback or its cleanup threw |
event |
A native event handler threw or rejected |
AbortError is filtered automatically — cancellation is the expected outcome of a signal abort, not a real error, so it never reaches handlers.
Multiple handlers
Declare multiple onError() calls and they run in declaration order. Each receives the same normalized error.
import { const ilha: IlhaFactoryilha, function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError } from "ilha";
const const track: (error: Error) => voidtrack = (error: Errorerror: Error) => {
void error: Errorerror;
};
const const App: Island<unknown>App = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => {
function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError(({ error: Errorerror }) => var console: Consoleconsole.Console.log(...data: any[]): voidThe **`console.log()`** static method outputs a message to the console.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)log("first", error: Errorerror));
function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError(({ error: Errorerror }) => const track: (error: Error) => voidtrack(error: Errorerror));
return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>hi</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
});
State, derived values, actions, and props stay in lexical closure, so each handler reads them directly:
import { const ilha: IlhaFactoryilha, function state<T>(init?: T | (() => T)): StateAccessor<T>Declare island-local reactive state at this call position. The initializer
applies only when the instance is created — later renders reuse the same
underlying signal, so prop-driven initializers never reset user state.
A function argument is treated as a lazy initializer:
const count = state(() => expensiveInitialValue());
To store a function VALUE, return it from the updater wrapper on write:
setCallback(() => nextCallback);state, function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError } from "ilha";
const const App: Island<unknown>App = ilha<unknown>(component: IslandComponent<unknown>): Island<unknown> (+3 overloads)ilha(() => {
const const count: StateAccessor<number>count = state<number>(init?: number | (() => number) | undefined): StateAccessor<number>Declare island-local reactive state at this call position. The initializer
applies only when the instance is created — later renders reuse the same
underlying signal, so prop-driven initializers never reset user state.
A function argument is treated as a lazy initializer:
const count = state(() => expensiveInitialValue());
To store a function VALUE, return it from the updater wrapper on write:
setCallback(() => nextCallback);state(0);
function onError(fn: (ctx: ErrorContext) => void): voidRegister an error handler slot for this island. Handlers run in
declaration order; state, derived values, actions, and props should be
accessed through lexical closure rather than the context object.onError(({ error: Errorerror }) => {
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("count was", const count: MarkedSignalAccessor
() => number (+1 overload)
count(), "when it failed");
});
return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{const count: MarkedSignalAccessor
() => number (+1 overload)
count()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
});
Global error sink — onUncaughtError()
Register an app-wide handler that fires when an island reports an error and has no local onError() handler. Returns an unsubscribe function:
import { function onUncaughtError(fn: (error: Error, source: ErrorSource) => void): () => voidRegister a global error handler invoked when any island reports an error
and has no local onError() handler. Returns an unsubscribe function.onUncaughtError } from "ilha";
const const telemetry: {
capture: (_error: unknown, _context: unknown) => void;
}
telemetry = {
capture: (_error: unknown, _context: unknown) => voidcapture: (_error: unknown_error: unknown, _context: unknown_context: unknown) => {},
};
const const stop: () => voidstop = function onUncaughtError(fn: (error: Error, source: ErrorSource) => void): () => voidRegister a global error handler invoked when any island reports an error
and has no local onError() handler. Returns an unsubscribe function.onUncaughtError((error: Errorerror, source: ErrorSourcesource) => {
const telemetry: {
capture: (_error: unknown, _context: unknown) => void;
}
telemetry.capture: (_error: unknown, _context: unknown) => voidcapture(error: Errorerror, { source: ErrorSourcesource });
});
// later
const stop: () => voidstop();
Islands with their own onError() are handled locally and do not reach the global sink.
Derived errors
Rejected async derived work sets derived.error on the envelope — it is not routed to onError(). Handle failures in the render path via the accessor’s loading / error / value properties:
import { const ilha: IlhaFactoryilha, function derived<V>(fn: (ctx: {
signal: AbortSignal;
}) => V | Promise<V> | AsyncIterable<V>): DerivedAccessor<V>
Declare derived state at this call position: synchronous computations,
Promises, or async generators. Reads track their dependencies; async work
races latest-run-wins and aborts stale runs through `ctx.signal`.derived } from "ilha";
const const UserCard: Island<{
id: string;
}>
UserCard = ilha<{
id: string;
}>(component: IslandComponent<{
id: string;
}>): Island<{
id: string;
}> (+3 overloads)
ilha<{ id: stringid: string }>(({ id: stringid }) => {
const const user: DerivedAccessor<any>user = derived<any>(fn: (ctx: {
signal: AbortSignal;
}) => any): DerivedAccessor<any>
Declare derived state at this call position: synchronous computations,
Promises, or async generators. Reads track their dependencies; async work
races latest-run-wins and aborts stale runs through `ctx.signal`.derived(async ({ signal: AbortSignalsignal }) => {
const const res: Responseres = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)fetch(`/api/users/${id: stringid}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.signal });
return const res: Responseres.Body.json(): Promise<any>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json();
});
if (const user: DerivedAccessor<any>user.error: Error | undefinederror) return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Error: {const user: DerivedAccessor<any>user.error: Errorerror.Error.message: stringmessage}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
if (const user: DerivedAccessor<any>user.loading: booleanloading) return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Loading…</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{const user: () => any (+1 overload)user()?.name}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
});
The fallback
When no onError() handler is registered and no global sink exists, ilha logs the error with console.error so it is never silently swallowed. If an onError() handler itself throws, ilha logs that handler error without recursing.