Skip to content
Ilha
Esc
navigateopen⌘Jpreview
On this page

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): void
Register 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 | undefined
A string to set request's method.
method
: "POST",
}); 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");
}); function onError(fn: (ctx: ErrorContext) => void): void
Register 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): void
Register 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): void
Register 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[]): void
The **`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): void
Register 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): void
Register 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): void
Register 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[]): 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
("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): () => void
Register 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): () => void
Register 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 | undefined
An 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.

Was this page helpful?