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

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 | undefined

An 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:

PropertyTypeDescription
loadingbooleantrue while the function is running
valueT | undefinedThe last successfully resolved value
errorError | undefinedSet 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 | undefined

An 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 | undefined

An 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 | undefined

An 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.