#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.
#Basic usage
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const UserCard: Island<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>> UserCard = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<number, "userId">(key: "userId", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, Record<string, never>> state ("userId", 1)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, Record<string, never>>.derived<"user", any>(key: "user", fn: DerivedFn<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, any>): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, Record<string, never> & Record<"user", any>> derived ("user", async ({ state: IslandState<MergeState<Record<string, never>, "userId", number>> state , signal: AbortSignal signal }) => {
const const res: Response res = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> fetch (`/api/users/${state: IslandState<MergeState<Record<string, never>, "userId", number>> state .userId: MarkedSignalAccessor
() => number (+1 overload)
userId ()}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.
signal });
return const res: Response res .Body.json(): Promise<any> json ();
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, Record<string, never> & Record<"user", any>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>, Record<string, never> & Record<"user", any>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "userId", number>> render (({ derived: IslandDerived<Record<string, never> & Record<"user", any>> derived }) => {
if (derived: IslandDerived<Record<string, never> & Record<"user", any>> derived .user: DerivedValue<any> user .DerivedValue<any>.loading: boolean loading ) return `<p>Loading…</p>`;
if (derived: IslandDerived<Record<string, never> & Record<"user", any>> derived .user: DerivedValue<any> user .DerivedValue<any>.error: Error | undefined error ) return `<p>Error: ${derived: IslandDerived<Record<string, never> & Record<"user", any>> derived .user: DerivedValue<any> user .DerivedValue<any>.error: Error error .Error.message: string message }</p>`;
return `<p>${derived: IslandDerived<Record<string, never> & Record<"user", any>> derived .user: DerivedValue<any> user .DerivedValue<any>.value: any value .name}</p>`;
});#The derived envelope
Every derived value exposes three properties:
| Property | Type | Description |
|---|---|---|
loading | boolean | true while the function is running |
value | T | undefined | The last successfully resolved value |
error | Error | undefined | Set if the function threw or rejected |
Always check loading and error before reading value. On first render, loading is true and value is undefined.
#Synchronous derived values
The function does not have to be async. If it returns a plain value, the envelope resolves immediately with loading: false:
import const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<number, "price">(key: "price", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "price", number>, Record<string, never>> state ("price", 100)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "price", number>, Record<string, never>>.state<number, "qty">(key: "qty", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, Record<string, never>> state ("qty", 3)
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, Record<string, never>>.derived<"total", number>(key: "total", fn: DerivedFn<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, number>): IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, Record<string, never> & Record<"total", number>> derived ("total", ({ state: IslandState<MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>> state }) => state: IslandState<MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>> state .price: MarkedSignalAccessor
() => number (+1 overload)
price () * state: IslandState<MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>> state .qty: MarkedSignalAccessor
() => number (+1 overload)
qty ())
.IlhaBuilder<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, Record<string, never> & Record<...>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>, Record<string, never> & Record<"total", number>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<MergeState<Record<string, never>, "price", number>, "qty", number>> render (({ derived: IslandDerived<Record<string, never> & Record<"total", number>> derived }) => `<p>Total: ${derived: IslandDerived<Record<string, never> & Record<"total", number>> derived .total: DerivedValue<number> total .DerivedValue<number>.value: number | undefined value }</p>`);#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<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const Search: Island<Record<string, unknown>, MergeState<Record<string, never>, "query", string>> Search = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<string, "query">(key: "query", init?: StateInit<Record<string, unknown>, string> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, Record<string, never>> state ("query", "")
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, Record<string, never>>.derived<"results", string[]>(key: "results", fn: DerivedFn<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, string[]>): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, Record<string, never> & Record<"results", string[]>> derived ("results", async ({ state: IslandState<MergeState<Record<string, never>, "query", string>> state , signal: AbortSignal signal }) => {
const const res: Response res = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> fetch (`/api/search?q=${state: IslandState<MergeState<Record<string, never>, "query", string>> state .query: MarkedSignalAccessor
() => string (+1 overload)
query ()}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.
signal });
return const res: Response res .Body.json(): Promise<any> json () as interface Promise<T>Represents the completion of an asynchronous operation
Promise <string[]>;
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, Record<string, never> & Record<"results", string[]>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "query", string>, Record<string, never> & Record<"results", string[]>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "query", string>> render (
({ state: IslandState<MergeState<Record<string, never>, "query", string>> state , derived: IslandDerived<Record<string, never> & Record<"results", string[]>> derived }) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `
<input value="${state: IslandState<MergeState<Record<string, never>, "query", string>> state .query: SignalAccessor<string> query }" />
${derived: IslandDerived<Record<string, never> & Record<"results", string[]>> derived .results: DerivedValue<string[]> results .DerivedValue<string[]>.loading: boolean loading
? const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<p>Searching…</p>`
: const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<ul>
${derived: IslandDerived<Record<string, never> & Record<"results", string[]>> derived .results: DerivedValue<string[]> results .DerivedValue<string[]>.value: string[] | undefined value ?.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: string r ) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<li>${r: string r }</li>`)}
</ul>`}
`,
);#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<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<string, never>, "id", number>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<number, "id">(key: "id", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, Record<string, never>> state ("id", 1)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, Record<string, never>>.derived<"data", any>(key: "data", fn: DerivedFn<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, any>): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, Record<string, never> & Record<"data", any>> derived ("data", async ({ state: IslandState<MergeState<Record<string, never>, "id", number>> state , signal: AbortSignal signal }) => {
const const res: Response res = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> fetch (`/api/items/${state: IslandState<MergeState<Record<string, never>, "id", number>> state .id: MarkedSignalAccessor
() => number (+1 overload)
id ()}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.
signal });
return const res: Response res .Body.json(): Promise<any> json ();
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, Record<string, never> & Record<"data", any>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "id", number>, Record<string, never> & Record<"data", any>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "id", number>> render (({ derived: IslandDerived<Record<string, never> & Record<"data", any>> derived }) => `<p>${derived: IslandDerived<Record<string, never> & Record<"data", any>> derived .data: DerivedValue<any> data .DerivedValue<any>.value: any value ?.name ?? "…"}</p>`);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<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha , { const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html } from "ilha";
const const Island: Island<Record<string, unknown>, MergeState<Record<string, never>, "page", number>> Island = const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, 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;
context: <T>(key: string, initial: T) => ContextSignal<...>;
}
ilha
.IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>>.state<number, "page">(key: "page", init?: StateInit<Record<string, unknown>, number> | undefined): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, Record<string, never>> state ("page", 1)
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, Record<string, never>>.derived<"items", string[]>(key: "items", fn: DerivedFn<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, string[]>): IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, Record<string, never> & Record<"items", string[]>> derived ("items", async ({ state: IslandState<MergeState<Record<string, never>, "page", number>> state , signal: AbortSignal signal }) => {
const const res: Response res = await function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> fetch (`/api/items?page=${state: IslandState<MergeState<Record<string, never>, "page", number>> state .page: MarkedSignalAccessor
() => number (+1 overload)
page ()}`, { RequestInit.signal?: AbortSignal | null | undefinedAn AbortSignal to set request's signal.
signal });
return const res: Response res .Body.json(): Promise<any> json () as interface Promise<T>Represents the completion of an asynchronous operation
Promise <string[]>;
})
.IlhaBuilder<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, Record<string, never> & Record<"items", string[]>>.render(fn: (ctx: RenderContext<Record<string, unknown>, MergeState<Record<string, never>, "page", number>, Record<string, never> & Record<"items", string[]>>) => string | RawHtml): Island<Record<string, unknown>, MergeState<Record<string, never>, "page", number>> render (
({ state: IslandState<MergeState<Record<string, never>, "page", number>> state , derived: IslandDerived<Record<string, never> & Record<"items", string[]>> derived }) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `
<ul style="opacity: ${derived: IslandDerived<Record<string, never> & Record<"items", string[]>> derived .items: DerivedValue<string[]> items .DerivedValue<string[]>.loading: boolean loading ? "0.5" : "1"}">
${derived: IslandDerived<Record<string, never> & Record<"items", string[]>> derived .items: DerivedValue<string[]> items .DerivedValue<string[]>.value: string[] | undefined value ?.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: string i ) => const html: (strings: TemplateStringsArray, ...values: unknown[]) => RawHtml html `<li>${i: string i }</li>`)}
</ul>
<button>Next page</button>
`,
);#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.
- 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.