Skip to content
Ilha
Esc
navigateopen⌘Jpreview
On this page

.stream()

Feed a state key from an async or sync generator. On the server ilha pulls only the first value; on the client it consumes the generator continuously.

.stream() declares a state key whose values come from a generator instead of a static initializer. It pairs well with server islands, live data, or any source you consume incrementally.

A streamed key is a state key: read and write it with the same signal accessor surface, and it participates in snapshots exactly like state(). It just starts undefined and is fed by the generator instead of a fixed initial value.

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;
    ... 6 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
} from "ilha";
const const Live: Island<RootInput, MergeState<RootState, "time", string>>Live =
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;
    ... 6 more ...;
    onUncaughtError: typeof onUncaughtError;
}
ilha
.IlhaBuilder<RootInput, RootState, RootDerived, RootActions>.stream<"time", string>(key: "time", fn: StreamFn<RootInput, string>): IlhaBuilder<RootInput, MergeState<RootState, "time", string>, RootDerived, RootActions>stream("time", async function* ({ signal: AbortSignal
Aborts when the island unmounts. Pass through to fetch/SSE layers.
signal
}) {
while (!signal: AbortSignal
Aborts when the island unmounts. Pass through to fetch/SSE layers.
signal
.AbortSignal.aborted: boolean
The **`aborted`** read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (true) or not (false). [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted)
aborted
) {
yield new
var Date: DateConstructor
new () => Date (+4 overloads)
Date
().Date.toISOString(): string
Returns a date as a string value in ISO format.
toISOString
();
await function sleep(ms: number): Promise<unknown>sleep(1000); } }) .IlhaBuilder<RootInput, MergeState<RootState, "time", string>, RootDerived, RootActions>.render(fn: (ctx: RenderContext<RootInput, MergeState<RootState, "time", string>, RootDerived, RootActions>) => string | RawHtml): Island<RootInput, MergeState<RootState, "time", string>>render(({ state: IslandState<MergeState<RootState, "time", string>>state }) => <"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>{state: IslandState<MergeState<RootState, "time", string>>state.
time: MarkedSignalAccessor
() => string (+1 overload)
time
()}</"p": WithRawHtmlAttributeValues<JSX.HTMLAttributes<HTMLParagraphElement>>p>);
function function sleep(ms: number): Promise<unknown>sleep(ms: numberms: number) { return new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Creates a new Promise.
@paramexecutor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.
Promise
((resolve: (value: unknown) => voidresolve) => function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout)
setTimeout
(resolve: (value: unknown) => voidresolve, ms: numberms));
}

How consumption differs by environment

The generator receives a single argument, { input, signal }:

  • input — resolved input props
  • signal — aborts when the island unmounts; pass it to fetch, SSE layers, or while loops to stop cleanly

On the server, ilha pulls only the first value so it can render inline — during SSR a streamed key renders its initial value. On the client, the generator is consumed continuously; each yielded value writes the state key and re-renders the island.

Declaring before render

Like other builder capabilities, declare .stream() before .render():

const Tasks = ilha
  .stream("items", ({ signal }) => getTasks({ signal }))
  .render(({ state }) => <ul>{/* … */}</ul>);

A stream that rejects during SSR degrades to its initial value instead of failing the render. On the client, an error from the generator is routed to the island error sink with source: "stream" unless it was cancelled by unmount.

Cleaning up

The unmount signal aborts when the island unmounts; ilha also aborts the stream controller itself. Cancel your while loop or in-flight fetch by checking signal.aborted so the generator can exit cleanly.

.stream() vs .derived() vs .effect()

.stream() .derived() .effect()
Produces State values over time One computed value Side effects
SSR First value only Awaited in async SSR Client-only
Re-runs Per yield On dependency change On dependency change
Use when Live/long-lived data Derived computation Imperative work

Was this page helpful?