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.
When a shorthand island needs computed data, expand ilha(() => JSX) into .derived(...).render(...). It remains the same kind of independently mounted island.
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 valueWhen 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 optimisticallyimport const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Cart: Island<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>>Cart = const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "price">(key: "price", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "price", number>, RootDerived, RootActions>state("price", 100)
.IlhaBuilder<RootInput, MergeState<RootState, "price", number>, RootDerived, RootActions>.state<number, "qty">(key: "qty", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived, RootActions>state("qty", 3)
.IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived, RootActions>.derived<"total", number>(key: "total", fn: DerivedFn<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, number>): IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>, RootActions>derived("total", ({ state: IslandState<MergeState<MergeState<RootState, "price", number>, "qty", number>>state }) => state: IslandState<MergeState<MergeState<RootState, "price", number>, "qty", number>>state.price: MarkedSignalAccessor
() => number (+1 overload)
price() * state: IslandState<MergeState<MergeState<RootState, "price", number>, "qty", number>>state.qty: MarkedSignalAccessor
() => number (+1 overload)
qty())
.IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<...>, RootActions>.action<"resetTotal", undefined, void>(key: "resetTotal", fn: (props: undefined, ctx: ActionContext<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>>) => void): IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>, RootActions & Record<...>>action("resetTotal", (_: undefined_, { derived: IslandDerived<RootDerived & Record<"total", number>>derived }) => derived: IslandDerived<RootDerived & Record<"total", number>>derived.total: (value: number) => void (+1 overload)total(0))
.IlhaBuilder<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<...>, RootActions & Record<...>>.render(fn: (ctx: RenderContext<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>, RootActions & Record<"resetTotal", (props: undefined, ctx: ActionContext<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>>) => void>>) => string | RawHtml): Island<...>render(({ derived: IslandDerived<RootDerived & Record<"total", number>>derived, action: IslandActions<RootActions & Record<"resetTotal", (props: undefined, ctx: ActionContext<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>>) => void>>action }) => (
<"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={action: IslandActions<RootActions & Record<"resetTotal", (props: undefined, ctx: ActionContext<RootInput, MergeState<MergeState<RootState, "price", number>, "qty", number>, RootDerived & Record<"total", number>>) => void>>action.resetTotal: ActionAccessor<undefined, void>resetTotal}>
Total: {derived: IslandDerived<RootDerived & Record<"total", number>>derived.total: () => number | undefined (+1 overload)total()}
</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>
));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:
| 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 |
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 errorFor 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: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const UserCard: Island<RootInput, MergeState<RootState, "userId", number>>UserCard = const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "userId">(key: "userId", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "userId", number>, RootDerived, RootActions>state("userId", 1)
.IlhaBuilder<RootInput, MergeState<RootState, "userId", number>, RootDerived, RootActions>.derived<"user", any>(key: "user", fn: DerivedFn<RootInput, MergeState<RootState, "userId", number>, any>): IlhaBuilder<RootInput, MergeState<RootState, "userId", number>, RootDerived & Record<"user", any>, RootActions>derived("user", async ({ state: IslandState<MergeState<RootState, "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<RootState, "userId", number>>state.userId: MarkedSignalAccessor
() => number (+1 overload)
userId()}`, {
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();
})
.IlhaBuilder<RootInput, MergeState<RootState, "userId", number>, RootDerived & Record<"user", any>, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "userId", number>, RootDerived & Record<"user", any>, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "userId", number>>render(({ derived: IslandDerived<RootDerived & Record<"user", any>>derived }) => {
if (derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: DerivedAccessor<any>user.loading: booleanloading) return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Loading…</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
if (derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: DerivedAccessor<any>user.error: Error | undefinederror)
return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Error: {derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: DerivedAccessor<any>user.error: Errorerror.Error.message: stringmessage}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
return <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{derived: IslandDerived<RootDerived & Record<"user", any>>derived.user: () => any (+1 overload)user().name}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>;
});On first render, loading is true and derived.user() is undefined until the promise resolves.
Cache fetches with @ilha/store/query
Return query() from island .derived() when you want cache, in-flight dedup, stale-while-revalidate, and invalidation across mounts. The island envelope stays the same — loading / value / error.
import ilha from "ilha";
import { query } from "@ilha/store/query";
type User = { name: string };
const UserCard = ilha
.state("userId", 1)
.derived("user", async ({ state, signal }) => {
const userId = state.userId();
return query({
key: ["user", userId],
fn: () =>
fetch(`/api/users/${userId}`, { signal }).then(
(r) => r.json() as Promise<User>,
),
staleTime: 30_000,
});
})
.render(({ derived }) => {
if (derived.user.loading) return <p>Loading…</p>;
if (derived.user.error)
return <p>Error: {derived.user.error.message}</p>;
return <p>{derived.user()?.name}</p>;
});Read reactive inputs (state.userId()) before building key. Capture the derived signal in fn so a re-run aborts the in-flight fetch. Full options, invalidate / invalidatePrefix, and SSR caveats live on @ilha/store.
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: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Search: Island<RootInput, MergeState<RootState, "query", string>>Search = const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<string, "query">(key: "query", init?: StateInit<RootInput, string> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "query", string>, RootDerived, RootActions>state("query", "")
.IlhaBuilder<RootInput, MergeState<RootState, "query", string>, RootDerived, RootActions>.derived<"results", string[]>(key: "results", fn: DerivedFn<RootInput, MergeState<RootState, "query", string>, string[]>): IlhaBuilder<RootInput, MergeState<RootState, "query", string>, RootDerived & Record<"results", string[]>, RootActions>derived("results", async ({ state: IslandState<MergeState<RootState, "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<RootState, "query", string>>state.query: MarkedSignalAccessor
() => string (+1 overload)
query()}`, {
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() as interface Promise<T>Represents the completion of an asynchronous operationPromise<string[]>;
})
.IlhaBuilder<RootInput, MergeState<RootState, "query", string>, RootDerived & Record<"results", string[]>, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "query", string>, RootDerived & Record<"results", string[]>, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "query", string>>render(({ state: IslandState<MergeState<RootState, "query", string>>state, derived: IslandDerived<RootDerived & Record<"results", string[]>>derived }) => (
<>
<"input": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLInputElement> & InputAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
input value?: string | number | RawHtml | undefinedvalue={state: IslandState<MergeState<RootState, "query", string>>state.query: MarkedSignalAccessor
() => string (+1 overload)
query()} />
{derived: IslandDerived<RootDerived & Record<"results", string[]>>derived.results: DerivedAccessor<string[]>results.loading: booleanloading ? (
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>Searching…</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>
) : (
<"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul>
{derived: IslandDerived<RootDerived & 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.map((r: stringr) => (
<"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
value?: number;
}>
li>{r: stringr}</"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
value?: number;
}>
li>
))}
</"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>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: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Island: Island<RootInput, MergeState<RootState, "id", number>>Island = const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "id">(key: "id", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "id", number>, RootDerived, RootActions>state("id", 1)
.IlhaBuilder<RootInput, MergeState<RootState, "id", number>, RootDerived, RootActions>.derived<"data", any>(key: "data", fn: DerivedFn<RootInput, MergeState<RootState, "id", number>, any>): IlhaBuilder<RootInput, MergeState<RootState, "id", number>, RootDerived & Record<"data", any>, RootActions>derived("data", async ({ state: IslandState<MergeState<RootState, "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<RootState, "id", number>>state.id: MarkedSignalAccessor
() => number (+1 overload)
id()}`, {
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();
})
.IlhaBuilder<RootInput, MergeState<RootState, "id", number>, RootDerived & Record<"data", any>, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "id", number>, RootDerived & Record<"data", any>, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "id", number>>render(({ derived: IslandDerived<RootDerived & Record<"data", any>>derived }) => (
<"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{derived: IslandDerived<RootDerived & Record<"data", any>>derived.data: () => any (+1 overload)data()?.name ?? "…"}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>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: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha from "ilha";
const const Island: Island<RootInput, MergeState<RootState, "page", number>>Island = const ilha: RootBuilder & DirectIslandFactory & {
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;
... 5 more ...;
onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.state<number, "page">(key: "page", init?: StateInit<RootInput, number> | undefined): IlhaBuilder<RootInput, MergeState<RootState, "page", number>, RootDerived, RootActions>state("page", 1)
.IlhaBuilder<RootInput, MergeState<RootState, "page", number>, RootDerived, RootActions>.derived<"items", string[]>(key: "items", fn: DerivedFn<RootInput, MergeState<RootState, "page", number>, string[]>): IlhaBuilder<RootInput, MergeState<RootState, "page", number>, RootDerived & Record<"items", string[]>, RootActions>derived("items", async ({ state: IslandState<MergeState<RootState, "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<RootState, "page", number>>state.page: MarkedSignalAccessor
() => number (+1 overload)
page()}`, {
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() as interface Promise<T>Represents the completion of an asynchronous operationPromise<string[]>;
})
.IlhaBuilder<RootInput, MergeState<RootState, "page", number>, RootDerived & Record<"items", string[]>, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "page", number>, RootDerived & Record<"items", string[]>, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "page", number>>render(({ derived: IslandDerived<RootDerived & Record<"items", string[]>>derived }) => (
<>
<"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul
style?: string | RawHtml | StyleProps | undefinedstyle={`opacity: ${derived: IslandDerived<RootDerived & Record<"items", string[]>>derived.items: DerivedAccessor<string[]>items.loading: booleanloading ? "0.5" : "1"}`}
>
{derived: IslandDerived<RootDerived & 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.map((i: stringi) => (
<"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
value?: number;
}>
li>{i: stringi}</"li": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLLIElement> & {
value?: number;
}>
li>
))}
</"ul": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLUListElement>>ul>
<"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
button>Next page</"button": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLButtonElement> & ButtonAttributes & {
popovertarget?: string;
popoverTarget?: string;
popovertargetaction?: "hide" | "show" | "toggle";
popoverTargetAction?: "hide" | "show" | "toggle";
}>
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.
- Rejected or thrown async derived work sets
derived.key.erroron the envelope — it is not routed to.onError(). Handle failures in the render path viaerror/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.