Derived

Declares a computed value that depends on state or input. Derived values can be synchronous or async, and they re-run automatically when any reactive dependency changes.

Interactive Tutorial

Basic usage

Each derived entry is a signal accessor — call it with no arguments to read the resolved value, the same way you read state.count():

derived.total(); // read → returns current value

When any signal the derived function reads changes, the function re-runs and the island re-renders.

Reading and writing

Derived accessors can also be written for optimistic UI. A write updates the value immediately without waiting for the derived function to re-run:

derived.total(999); // write → sets value optimistically
import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha"; const const Cart: Island<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>>Cart =
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<number, "price">(key: "price", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "price", number>, Record<never, never>>state("price", 100) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "price", number>, Record<never, never>>.state<number, "qty">(key: "qty", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never>>state("qty", 3) .IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never>>.derived<"total", number>(key: "total", fn: DerivedFn<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, number>): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never> & Record<"total", number>>derived("total", ({ state: IslandState<MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>>state }) => state: IslandState<MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>>state.
price: MarkedSignalAccessor
() => number (+1 overload)
price
() * state: IslandState<MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>>state.
qty: MarkedSignalAccessor
() => number (+1 overload)
qty
())
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never> & Record<...>>.on<"button@click">(selectorOrCombined: "button@click", handler: (ctx: HandlerContextFor<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, "click", Record<never, never> & Record<"total", number>>) => void | Promise<void>): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never> & Record<...>>on("button@click", ({ derived: IslandDerived<Record<never, never> & Record<"total", number>>derived }) => derived: IslandDerived<Record<never, never> & Record<"total", number>>derived.total: (value: number) => void (+1 overload)total(0)) .IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never> & Record<...>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>, Record<never, never> & Record<"total", number>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<MergeState<Record<never, never>, "price", number>, "qty", number>>render(({ derived: IslandDerived<Record<never, never> & Record<"total", number>>derived }) => <IntrinsicElements[string]: anyp>Total: {derived: IslandDerived<Record<never, never> & Record<"total", number>>derived.total: () => number | undefined (+1 overload)total()}</IntrinsicElements[string]: anyp>);

The next time the derived function runs (for example after state changes or an async fetch resolves), it overwrites the optimistic value with the computed result.

The derived envelope

Every derived value also exposes loading, value, and error on the accessor itself. Use these when you need explicit control — especially with async derived values:

PropertyTypeDescription
loadingbooleantrue while the function is running
valueT | undefinedThe last successfully resolved value
errorError | undefinedSet if the function threw or rejected
derived.total(); // same as derived.total.value when resolved
derived.total.value; // envelope read
derived.total.loading; // false for sync derived after first run
derived.total.error; // undefined when no error

For synchronous derived values, loading is false after the first run and () returns the computed value directly. For async derived values, check loading and error before reading value or calling ().

Async derived values

Pass an async function to fetch data or run any other asynchronous work. The envelope tracks progress while the promise is pending:

import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha"; const const UserCard: Island<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>>UserCard =
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<number, "userId">(key: "userId", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, Record<never, never>>state("userId", 1) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, Record<never, never>>.derived<"user", any>(key: "user", fn: DerivedFn<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, any>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, Record<never, never> & Record<"user", any>>derived("user", async ({ state: IslandState<MergeState<Record<never, never>, "userId", number>>state, 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/${state: IslandState<MergeState<Record<never, never>, "userId", number>>state.
userId: MarkedSignalAccessor
() => number (+1 overload)
userId
()}`, {
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
();
}) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, Record<never, never> & Record<"user", any>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>, Record<never, never> & Record<"user", any>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "userId", number>>render(({ derived: IslandDerived<Record<never, never> & Record<"user", any>>derived }) => { if (derived: IslandDerived<Record<never, never> & Record<"user", any>>derived.user: DerivedAccessor<any>user.loading: booleanloading) return <IntrinsicElements[string]: anyp>Loading…</IntrinsicElements[string]: anyp>; if (derived: IslandDerived<Record<never, never> & Record<"user", any>>derived.user: DerivedAccessor<any>user.error: Error | undefinederror) return <IntrinsicElements[string]: anyp>Error: {derived: IslandDerived<Record<never, never> & Record<"user", any>>derived.user: DerivedAccessor<any>user.error: Errorerror.Error.message: stringmessage}</IntrinsicElements[string]: anyp>; return <IntrinsicElements[string]: anyp>{derived: IslandDerived<Record<never, never> & Record<"user", any>>derived.user: () => any (+1 overload)user().name}</IntrinsicElements[string]: anyp>; });

On first render, loading is true and derived.user() is undefined until the promise resolves.

Reactive dependencies

The derived function re-runs whenever any signal it reads changes. Dependencies are tracked automatically — you do not need to declare them manually.

import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha"; const const Search: Island<Record<string, unknown>, MergeState<Record<never, never>, "query", string>>Search =
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<string, "query">(key: "query", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never>>state("query", "") .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never>>.derived<"results", string[]>(key: "results", fn: DerivedFn<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, string[]>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never> & Record<"results", string[]>>derived("results", async ({ state: IslandState<MergeState<Record<never, never>, "query", string>>state, 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/search?q=${state: IslandState<MergeState<Record<never, never>, "query", string>>state.
query: MarkedSignalAccessor
() => string (+1 overload)
query
()}`, {
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
() as interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<string[]>;
}) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never> & Record<"results", string[]>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "query", string>, Record<never, never> & Record<"results", string[]>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "query", string>>render(({ state: IslandState<MergeState<Record<never, never>, "query", string>>state, derived: IslandDerived<Record<never, never> & Record<"results", string[]>>derived }) => ( <> <IntrinsicElements[string]: anyinput value: stringvalue={state: IslandState<MergeState<Record<never, never>, "query", string>>state.
query: MarkedSignalAccessor
() => string (+1 overload)
query
()} />
{derived: IslandDerived<Record<never, never> & Record<"results", string[]>>derived.results: DerivedAccessor<string[]>results.loading: booleanloading ? ( <IntrinsicElements[string]: anyp>Searching…</IntrinsicElements[string]: anyp> ) : ( <IntrinsicElements[string]: anyul> {derived: IslandDerived<Record<never, never> & Record<"results", string[]>>derived.results: () => string[] | undefined (+1 overload)results()?.Array<string>.map<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any): U[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((r: stringr) => (
<IntrinsicElements[string]: anyli>{r: stringr}</IntrinsicElements[string]: anyli> ))} </IntrinsicElements[string]: anyul> )} </> ));

Abort signal

Every async derived function receives an AbortSignal that aborts when the function is about to re-run. Pass it to fetch or any other cancellable API to avoid stale responses:

import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha"; const const Island: Island<Record<string, unknown>, MergeState<Record<never, never>, "id", number>>Island =
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<number, "id">(key: "id", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, Record<never, never>>state("id", 1) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, Record<never, never>>.derived<"data", any>(key: "data", fn: DerivedFn<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, any>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, Record<never, never> & Record<"data", any>>derived("data", async ({ state: IslandState<MergeState<Record<never, never>, "id", number>>state, 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/items/${state: IslandState<MergeState<Record<never, never>, "id", number>>state.
id: MarkedSignalAccessor
() => number (+1 overload)
id
()}`, {
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
();
}) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, Record<never, never> & Record<"data", any>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "id", number>, Record<never, never> & Record<"data", any>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "id", number>>render(({ derived: IslandDerived<Record<never, never> & Record<"data", any>>derived }) => ( <IntrinsicElements[string]: anyp>{derived: IslandDerived<Record<never, never> & Record<"data", any>>derived.data: () => any (+1 overload)data()?.name ?? ""}</IntrinsicElements[string]: anyp> ));

If the signal was already aborted before your async work completes, the result is discarded silently.

Keeping stale value during reload

When a derived function re-runs, loading becomes true but value retains the previous result until the new one resolves. This lets you avoid layout shifts by showing stale content while refreshing:

import 
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha"; const const Island: Island<Record<string, unknown>, MergeState<Record<never, never>, "page", number>>Island =
const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
    html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml;
    raw: (value: string) => RawHtml;
    mount: (registry: IslandRegistry, options?: MountOptions) => MountResult;
    from: <TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>) => (() => void) | null;
    ... 4 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>>.state<number, "page">(key: "page", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, Record<never, never>>state("page", 1) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, Record<never, never>>.derived<"items", string[]>(key: "items", fn: DerivedFn<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, string[]>): IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, Record<never, never> & Record<"items", string[]>>derived("items", async ({ state: IslandState<MergeState<Record<never, never>, "page", number>>state, 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/items?page=${state: IslandState<MergeState<Record<never, never>, "page", number>>state.
page: MarkedSignalAccessor
() => number (+1 overload)
page
()}`, {
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
() as interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<string[]>;
}) .IlhaBuilder<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, Record<never, never> & Record<"items", string[]>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<never, never>, "page", number>, Record<never, never> & Record<"items", string[]>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<never, never>, "page", number>>render(({ derived: IslandDerived<Record<never, never> & Record<"items", string[]>>derived }) => ( <> <IntrinsicElements[string]: anyul style: stringstyle={`opacity: ${derived: IslandDerived<Record<never, never> & Record<"items", string[]>>derived.items: DerivedAccessor<string[]>items.loading: booleanloading ? "0.5" : "1"}`} > {derived: IslandDerived<Record<never, never> & Record<"items", string[]>>derived.items: () => string[] | undefined (+1 overload)items()?.Array<string>.map<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any): U[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
((i: stringi) => (
<IntrinsicElements[string]: anyli>{i: stringi}</IntrinsicElements[string]: anyli> ))} </IntrinsicElements[string]: anyul> <IntrinsicElements[string]: anybutton>Next page</IntrinsicElements[string]: anybutton> </> ));

SSR behavior

During SSR, derived functions are called once. If they are async, the island awaits them before rendering when called as await island(props). When called synchronously via island.toString(), async derived values render with loading: true immediately.

// Async — waits for all derived values to resolve
const html = await MyIsland({ userId: 1 });

// Sync — derived renders in loading state
const html = MyIsland.toString({ userId: 1 });

Hydration snapshots

When using .hydratable() with snapshot: true, derived values are embedded in the server output and restored on the client. This means the island can render immediately on mount without re-fetching, using the server-resolved value as the initial state.

See .hydratable() for full snapshot options.

Notes

  • Derived keys must be unique within the same builder chain.
  • Rejected or thrown async derived work sets derived.key.error on the envelope — it is not routed to .onError(). Handle failures in the render path via error / loading.
  • Async schemas are not supported as derived functions — the function itself can be async, but ilha .input() schemas must remain synchronous.
  • Multiple derived entries are independent. Each tracks its own dependencies and re-runs on its own schedule.