Skip to content
Ilha
Esc
↑↓navigate↵open⌘Jpreview
On this page

State management

Declare reactive values with atom(), derive with Effect Atom.map and Atom.transform, and react with watch() or watch.once().

atom()

Declare local state with atom(). Reads during render subscribe the component; writes rerun it.

import { const atom: AtomFnatom } from "ilha";

const const Counter: () => ViewCounter = () => {
  const const count: AtomHandle<number>count = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0);
  return (
    <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={() => const count: AtomHandle<number>count.AtomHandle<number>.update: (f: (current: number) => number) => voidupdate((n: numbern) => n: numbern + 1)}>
      {const count: AtomHandle<number>count}
    </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton>
  );
};
Call Meaning
count() Read the current value
{count} in JSX Read and subscribe
count.set(1) Replace
count.update((n) => n + 1) Patch from previous

Where you read matters

A read in the component body (count() or String(count())) subscribes the whole component to rerun. Passing the handle in JSX ({count}) gives that node its own subscription and updates only that subtree. Prefer {count} in markup when you do not need the value in logic.

Use untrack(() => …) to read atoms during render without subscribing:

import { const atom: AtomFnatom, const untrack: <A>(fn: () => A) => A
Read atoms inside `fn` without subscribing the active render. Sync only.
untrack
} from "ilha";
const const Panel: () => ViewPanel = () => { const const a: AtomHandle<number>a = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0); const const b: AtomHandle<number>b = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(10); const const peeked: numberpeeked = untrack<number>(fn: () => number): number
Read atoms inside `fn` without subscribing the active render. Sync only.
untrack
(() =>
const b: AtomHandle
() => number
b
());
return ( <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p> {const a: AtomHandle<number>a}:{const peeked: numberpeeked} </JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p> ); };

untrack is sync only. Prefer {handle} or a derived atom(Atom.map(...)) when you need the view to update.

In async components, declare atoms before the first await. After an await, pass handles in JSX instead of calling them in the body — only the synchronous setup pass collects render dependencies.

Derived atoms

Pass handle.atom to Effect, wrap the result in atom():

import * as import AtomAtom from "effect/reactivity/Atom";
import { const atom: AtomFnatom } from "ilha";

const const Cart: () => ViewCart = () => {
  const 
const items: AtomHandle<{
    id: string;
    n: number;
}[]>
items
=
atom<{
    id: string;
    n: number;
}[]>(init: {
    id: string;
    n: number;
}[] | Atom.Atom<{
    id: string;
    n: number;
}[]> | Effect<{
    id: string;
    n: number;
}[], unknown, AtomRegistry> | Stream<{
    id: string;
    n: number;
}[], unknown, AtomRegistry>, options?: AtomOptions<{
    id: string;
    n: number;
}[]> | undefined): AtomHandle<{
    id: string;
    n: number;
}[]>
atom
([
{ id: stringid: "a", n: numbern: 1 }, { id: stringid: "b", n: numbern: 2 }, ]); const const total: AtomHandle<number>total = atom<number>(init: number | Atom.Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom( import AtomAtom.
const map: <Atom.Atom<{
    id: string;
    n: number;
}[]>, number>(self: Atom.Atom<{
    id: string;
    n: number;
}[]>, f: (_: {
    id: string;
    n: number;
}[]) => number) => Atom.Atom<number> (+1 overload)
Maps the current value of an atom with a pure function. **Details** When the source atom is writable, the returned atom remains writable and keeps the source atom's write input type.
@stabilityunstable@categorycombinators@since4.0.0
map
(
const items: AtomHandle<{
    id: string;
    n: number;
}[]>
items
.
AtomHandle<{ id: string; n: number; }[]>.atom: Atom.Atom<{
    id: string;
    n: number;
}[]>
atom
, (
list: {
    id: string;
    n: number;
}[]
list
) =>
list: {
    id: string;
    n: number;
}[]
list
.
Array<{ id: string; n: number; }>.reduce<number>(callbackfn: (previousValue: number, currentValue: {
    id: string;
    n: number;
}, currentIndex: number, array: {
    id: string;
    n: number;
}[]) => number, initialValue: number): number (+2 overloads)
Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
@paramcallbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.@paraminitialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
reduce
((sum: numbersum,
item: {
    id: string;
    n: number;
}
item
) => sum: numbersum +
item: {
    id: string;
    n: number;
}
item
.n: numbern, 0))
); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{const total: AtomHandle<number>total}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; };
API Use when
Atom.map(source, f) Pure function of one atom
Atom.transform(source, (get, source) => …) Reads more than one atom via get
import * as import AtomAtom from "effect/reactivity/Atom";
import { const atom: AtomFnatom } from "ilha";

const const Form: () => ViewForm = () => {
  const const first: AtomHandle<string>first = atom<string>(init: string | Atom.Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom("");
  const const last: AtomHandle<string>last = atom<string>(init: string | Atom.Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom("");
  const const fullName: AtomHandle<string>fullName = atom<string>(init: string | Atom.Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom(
    import AtomAtom.
const transform: <Atom.Atom<string>, string>(self: Atom.Atom<string>, f: (get: Atom.AtomContext, atom: Atom.Atom<string>) => string, options?: {
    readonly initialValueTarget?: Atom.Atom<string> | undefined;
} | undefined) => Atom.Atom<string> (+1 overload)
Creates a derived atom by reading another atom with a custom `AtomContext` function. **Details** If the source is writable, the derived atom keeps the source write input and forwards writes to the source. `initialValueTarget` controls which atom receives preloaded initial values for the derived atom.
@stabilityunstable@categorycombinators@since4.0.0
transform
(const first: AtomHandle<string>first.AtomHandle<string>.atom: Atom.Atom<string>atom, (get: Atom.AtomContextget) =>
`${
get: Atom.AtomContext
<string>(atom: Atom.Atom<string>) => string
get
(const first: AtomHandle<string>first.AtomHandle<string>.atom: Atom.Atom<string>atom)} ${
get: Atom.AtomContext
<string>(atom: Atom.Atom<string>) => string
get
(const last: AtomHandle<string>last.AtomHandle<string>.atom: Atom.Atom<string>atom)}`.String.trim(): string
Removes the leading and trailing white space and line terminator characters from a string.
trim
()
) ); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{const fullName: AtomHandle<string>fullName}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; };

Side effects

Render subscription reruns the component — it is not useEffect. Use watch() for callbacks on atom changes:

import { const atom: AtomFnatom, const watch: WatchFnwatch } from "ilha";

const const Profile: () => ViewProfile = () => {
  const const name: AtomHandle<string>name = atom<string>(init: string | Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom("john");
  watch<string>(source: Atom<string> | AtomHandle<string> | Stream<string, unknown, unknown>, fn: WatchCallback<string>): Instruction<undefined>watch(const name: AtomHandle<string>name, (value: stringvalue) => {
    var localStorage: Storage
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
localStorage
.Storage.setItem(key: string, value: string): void
The **`setItem()`** method of the Storage interface, when passed a key name and value, will add that key to the given Storage object, or update that key's value if it already exists. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Storage/setItem)
setItem
("profile:name", value: stringvalue);
}); return ( <JSX.IntrinsicElements.input: InputHTMLAttributesinput value?: Bindable<string | number> | undefinedvalue={const name: AtomHandle<string>name} EventProps<HTMLInputElement>.oninput?: ((event: Targeted<HTMLInputElement, Event>) => void) | undefinedoninput={(e: Targeted<HTMLInputElement, Event>e) => const name: AtomHandle<string>name.AtomHandle<string>.set: (next: string) => voidset(e: Targeted<HTMLInputElement, Event>e.currentTarget: HTMLInputElementcurrentTarget.HTMLInputElement.value: string
The **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
)} />
); };

For one-shot mount work, use watch.once(). Return a function to clean up on unmount:

import { const watch: WatchFnwatch } from "ilha";

const const Boot: () => ViewBoot = () => {
  const watch: WatchFnwatch.WatchFn.once: (fn: (ctx: WatchContext) => WatchOnceCleanup | undefined | Promise<WatchOnceCleanup | undefined>) => Instruction<undefined>once(() => {
    const const onKey: (e: KeyboardEvent) => voidonKey = (e: KeyboardEvente: KeyboardEvent) => {
      if (e: KeyboardEvente.KeyboardEvent.key: string
The KeyboardEvent interface's **`key`** read-only property returns the value of the key pressed by the user, taking into consideration the state of modifier keys such as Shift as well as the keyboard locale and layout. [MDN Reference](https://developer.mozilla.org/docs/Web/API/KeyboardEvent/key)
key
=== "Escape") {
var console: Console
The `console` module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers. The module exports two specific components: * A `Console` class with methods such as `console.log()`, `console.error()` and `console.warn()` that can be used to write to any Node.js stream. * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstdout) and [`process.stderr`](https://nodejs.org/docs/latest-v24.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module. _**Warning**_: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v24.x/api/process.html#a-note-on-process-io) for more information. Example using the global `console`: ```js console.log('hello world'); // Prints: hello world, to stdout console.log('hello %s', 'world'); // Prints: hello world, to stdout console.error(new Error('Whoops, something bad happened')); // Prints error message and stack trace to stderr: // Error: Whoops, something bad happened // at [eval]:5:15 // at Script.runInThisContext (node:vm:132:18) // at Object.runInThisContext (node:vm:309:38) // at node:internal/process/execution:77:19 // at [eval]-wrapper:6:22 // at evalScript (node:internal/process/execution:76:60) // at node:internal/main/eval_string:23:3 const name = 'Will Robinson'; console.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to stderr ``` Example using the `Console` class: ```js const out = getStreamSomehow(); const err = getStreamSomehow(); const myConsole = new console.Console(out, err); myConsole.log('hello world'); // Prints: hello world, to out myConsole.log('hello %s', 'world'); // Prints: hello world, to out myConsole.error(new Error('Whoops, something bad happened')); // Prints: [Error: Whoops, something bad happened], to err const name = 'Will Robinson'; myConsole.warn(`Danger ${name}! Danger!`); // Prints: Danger Will Robinson! Danger!, to err ```
@see[source](https://github.com/nodejs/node/blob/v24.x/lib/console.js)
console
.Console.log(message?: any, ...optionalParams: any[]): void (+2 overloads)
Prints to `stdout` with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to [`printf(3)`](http://man7.org/linux/man-pages/man3/printf.3.html) (the arguments are all passed to [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args)). ```js const count = 5; console.log('count: %d', count); // Prints: count: 5, to stdout console.log('count:', count); // Prints: count: 5, to stdout ``` See [`util.format()`](https://nodejs.org/docs/latest-v24.x/api/util.html#utilformatformat-args) for more information.
@sincev0.1.100
log
("esc");
} }; var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.addEventListener<"keydown">(type: "keydown", listener: (this: Window, ev: KeyboardEvent) => any, options?: boolean | AddEventListenerOptions): void (+3 overloads)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
("keydown", const onKey: (e: KeyboardEvent) => voidonKey);
return () => var window: Window & typeof globalThis
The **`window`** property of a Window object points to the window object itself. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)
window
.removeEventListener<"keydown">(type: "keydown", listener: (this: Window, ev: KeyboardEvent) => any, options?: boolean | EventListenerOptions): void (+3 overloads)
The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. The event listener to be removed is identified using a combination of the event type, the event listener function itself, and various optional options that may affect the matching process; see Matching event listeners for removal. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)
removeEventListener
("keydown", const onKey: (e: KeyboardEvent) => voidonKey);
}); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>ready</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; };

watch.once() accepts an async callback and hands you an abort signal. The signal aborts on unmount:

import { const watch: WatchFnwatch } from "ilha";

const const Feed: () => ViewFeed = () => {
  const watch: WatchFnwatch.WatchFn.once: (fn: (ctx: WatchContext) => WatchOnceCleanup | undefined | Promise<WatchOnceCleanup | undefined>) => Instruction<undefined>once(async ({ signal: AbortSignalsignal }) => {
    const const socket: WebSocketsocket = new var WebSocket: new (url: string | URL, protocols?: string | string[]) => WebSocket
The **`WebSocket`** object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) A WebSocket client implementation.
WebSocket
("wss://example.com/live");
signal: AbortSignalsignal.AbortSignal.addEventListener<"abort">(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)
The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target. [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)
addEventListener
("abort", () => const socket: WebSocketsocket.WebSocket.close(code?: number, reason?: string): void
The **`WebSocket.close()`** method closes the WebSocket connection or connection attempt, if any. If the connection is already CLOSED, this method does nothing. [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close)
close
());
}); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>live</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; };

watch(source, fn) passes the same context per run. A new value aborts the previous run before fn executes — stale work never overwrites fresh results:

import { const atom: AtomFnatom, const watch: WatchFnwatch } from "ilha";

const const Search: () => ViewSearch = () => {
  const const query: AtomHandle<string>query = atom<string>(init: string | Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom("");
  watch<string>(source: Atom<string> | AtomHandle<string> | Stream<string, unknown, unknown>, fn: WatchCallback<string>): Instruction<undefined>watch(const query: AtomHandle<string>query, (q: stringq, { signal: AbortSignalsignal }) => {
    void function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+3 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(`/api/search?q=${q: stringq}`, { RequestInit.signal?: AbortSignal | null | undefined
An AbortSignal to set request's signal.
signal
});
}); return ( <JSX.IntrinsicElements.input: InputHTMLAttributesinput value?: Bindable<string | number> | undefinedvalue={const query: AtomHandle<string>query} EventProps<HTMLInputElement>.oninput?: ((event: Targeted<HTMLInputElement, Event>) => void) | undefinedoninput={(e: Targeted<HTMLInputElement, Event>e) => const query: AtomHandle<string>query.AtomHandle<string>.set: (next: string) => voidset(e: Targeted<HTMLInputElement, Event>e.currentTarget: HTMLInputElementcurrentTarget.HTMLInputElement.value: string
The **`value`** property of the HTMLInputElement interface represents the current value of the <input> element as a string. [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLInputElement/value)
value
)} />
); };

Call watch() / watch.once() before any await in async components. Guard DOM access on the server. For streams and when, see Streams.

Batching writes

batch() notifies subscribers once after multiple writes:

import { const atom: AtomFnatom, const batch: (f: () => void) => voidbatch } from "ilha";

const const Panel: () => ViewPanel = () => {
  const const a: AtomHandle<number>a = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0);
  const const b: AtomHandle<number>b = atom<number>(init: number | Atom<number> | Effect<number, unknown, AtomRegistry> | Stream<number, unknown, AtomRegistry>, options?: AtomOptions<number> | undefined): AtomHandle<number>atom(0);
  const const reset: () => voidreset = () => {
    function batch(f: () => void): voidbatch(() => {
      const a: AtomHandle<number>a.AtomHandle<number>.set: (next: number) => voidset(0);
      const b: AtomHandle<number>b.AtomHandle<number>.set: (next: number) => voidset(0);
    });
  };
  return (
    <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="button" EventProps<HTMLButtonElement>.onclick?: ((event: Targeted<HTMLButtonElement, MouseEvent>) => void) | undefinedonclick={const reset: () => voidreset}>
      {const a: AtomHandle<number>a}-{const b: AtomHandle<number>b}
    </JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton>
  );
};

Skip equal writes

Atoms notify on reference change. You pass equals to skip subscribers when the value is meaningfully the same:

import { const atom: AtomFnatom } from "ilha";

const const Profile: () => ViewProfile = () => {
  const 
const user: AtomHandle<{
    name: string;
}>
user
=
atom<{
    name: string;
}>(init: {
    name: string;
} | Atom<{
    name: string;
}> | Effect<{
    name: string;
}, unknown, AtomRegistry> | Stream<{
    name: string;
}, unknown, AtomRegistry>, options?: AtomOptions<{
    name: string;
}> | undefined): AtomHandle<{
    name: string;
}>
atom
({ name: stringname: "Ada" }, {
AtomOptions<{ name: string; }>.equals?: AtomEquality<{
    name: string;
}> | undefined
Skip subscriber notify when `equals(prev, next)` holds. Plain values only.
equals
: "structural" });
return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
(
const user: AtomHandle
() => {
    name: string;
}
user
())}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
};

user.set({ name: "Ada" }) does not rerun subscribers. Pass a comparator for custom rules:

import { const atom: AtomFnatom } from "ilha";

const const Name: () => ViewName = () => {
  const const name: AtomHandle<string>name = atom<string>(init: string | Atom<string> | Effect<string, unknown, AtomRegistry> | Stream<string, unknown, AtomRegistry>, options?: AtomOptions<string> | undefined): AtomHandle<string>atom("Ada", {
    AtomOptions<string>.equals?: AtomEquality<string> | undefined
Skip subscriber notify when `equals(prev, next)` holds. Plain values only.
equals
: (a: stringa, b: stringb) => a: stringa.String.toLowerCase(): string
Converts all the alphabetic characters in a string to lowercase.
toLowerCase
() === b: stringb.String.toLowerCase(): string
Converts all the alphabetic characters in a string to lowercase.
toLowerCase
(),
}); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{const name: AtomHandle<string>name}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>; };

equals applies to plain values only. Like the initial value, it is fixed by the first call and never resets.

Async mutations

atom(Atom.fn(...)) runs an Effect when you call .set():

import * as import AtomAtom from "effect/reactivity/Atom";
import * as import EffectEffect from "effect/Effect";
import { const atom: AtomFnatom } from "ilha";

const const Form: () => ViewForm = () => {
  const const save: AtomHandle<AsyncResult<any, unknown>>save = atom<AsyncResult<any, unknown>>(init: AsyncResult<any, unknown> | Atom.Atom<AsyncResult<any, unknown>> | Effect.Effect<AsyncResult<any, unknown>, unknown, AtomRegistry> | Stream<AsyncResult<any, unknown>, unknown, AtomRegistry>, options?: AtomOptions<AsyncResult<any, unknown>> | undefined): AtomHandle<AsyncResult<any, unknown>>atom(
    import AtomAtom.
const fn: <unknown, any, string>(fn: (arg: string, get: Atom.FnContext) => Effect.Effect<any, unknown, AtomRegistry | Scope>, options?: {
    readonly initialValue?: any;
    readonly concurrent?: boolean | undefined;
} | undefined) => Atom.AtomResultFn<string, any, unknown> (+3 overloads)
Creates a writable atom for an `Effect` or `Stream` function; writing an argument starts the computation and exposes its state as an `AsyncResult`.
@stabilityunstable@categoryconstructors@since4.0.0
fn
((email: stringemail: string) =>
import EffectEffect.
const tryPromise: <any, unknown>(options: {
    readonly try: (signal: AbortSignal) => PromiseLike<any>;
    readonly catch: (error: unknown) => unknown;
}) => Effect.Effect<any, unknown, never> (+1 overload)
Creates an `Effect` from an asynchronous computation that may throw or reject, mapping failures into the error channel. **When to use** Use when you need to perform asynchronous operations that might fail, such as fetching data from an API, and want thrown exceptions or rejected promises captured as Effect errors. **Details** The promise thunk is evaluated when the effect runs. If it returns a promise that resolves, the resolved value becomes the success value. If the thunk throws before returning a promise, or if the returned promise rejects, the thrown or rejected value is mapped into the error channel. Passing the thunk directly maps failures to {@link Cause.UnknownError } . Passing `{ try, catch }` uses `catch` to map failures to an error of type `E`. The thunk receives an `AbortSignal` that is aborted if the effect is interrupted. The underlying asynchronous operation only stops if it observes that signal. **Gotchas** If `catch` throws while mapping the error, that thrown value is treated as a defect. Return the error value you want in the error channel instead of throwing it. **Example** (Wrapping a fetch request that may fail) ```ts import.meta.vitest import { Effect } from "effect" const getTodo = (id: number) => Effect.tryPromise(() => Promise.resolve({ id, completed: false })) // ┌─── Effect<{ id: number; completed: boolean }, UnknownError, never> // ▼ const program = getTodo(1) await Effect.runPromise(program) // => { id: 1, completed: false } ``` **Example** (Mapping Promise rejections to a tagged error) ```ts import.meta.vitest import { Data, Effect } from "effect" class TodoFetchError extends Data.TaggedError("TodoFetchError")<{ readonly cause: unknown }> {} const getTodo = (id: number) => Effect.tryPromise({ try: () => Promise.reject(`Todo ${id} is unavailable`), // remap the error catch: (cause) => new TodoFetchError({ cause }) }) // ┌─── Effect<never, TodoFetchError, never> // ▼ const program = Effect.flip(getTodo(1)) const error = await Effect.runPromise(program) error._tag // => "TodoFetchError" ```
@see{@link promise} if the effectful computation is asynchronous and does not throw errors.@categoryconstructors@since2.0.0
tryPromise
({
try: (signal: AbortSignal) => PromiseLike<any>try: () => function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+3 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
("/api/waitlist", {
RequestInit.method?: string | undefined
A string to set request's method.
method
: "POST",
RequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
body
: var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
({ email: stringemail }),
}).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((r: Responser) => r: Responser.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
()),
catch: (error: unknown) => unknowncatch: (e: unknowne) => e: unknowne, }) ) ); return ( <JSX.IntrinsicElements.form: FormHTMLAttributesform EventProps<HTMLFormElement>.onsubmit?: ((event: Targeted<HTMLFormElement, SubmitEvent>) => void) | undefinedonsubmit={(e: Targeted<HTMLFormElement, SubmitEvent>e) => { e: Targeted<HTMLFormElement, SubmitEvent>e.function preventDefault(): void
The **`preventDefault()`** method of the Event interface tells the user agent that the event is being explicitly handled, so its default action, such as page scrolling, link navigation, or pasting text, should not be taken. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault)
preventDefault
();
const const email: stringemail =
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String
(new var FormData: new (form?: HTMLFormElement, submitter?: HTMLElement | null) => FormData
The **`FormData`** interface provides a way to construct a set of key/value pairs representing form fields and their values, which can be sent using the fetch(), XMLHttpRequest.send() or navigator.sendBeacon() methods. It uses the same format a form would use if the encoding type were set to "multipart/form-data". [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData)
FormData
(e: Targeted<HTMLFormElement, SubmitEvent>e.currentTarget: HTMLFormElementcurrentTarget).FormData.get(name: string): Bun.FormDataEntryValue | null (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get)
get
("email") ?? "");
if (import AtomAtom.const isWritable: <AsyncResult<any, unknown>, unknown>(atom: Atom.Atom<AsyncResult<any, unknown>>) => atom is Atom.Writable<AsyncResult<any, unknown>, unknown>
Returns `true` when an atom is writable.
@stabilityunstable@categoryguards@since4.0.0
isWritable
(const save: AtomHandle<AsyncResult<any, unknown>>save.AtomHandle<AsyncResult<any, unknown>>.atom: Atom.Atom<AsyncResult<any, unknown>>atom)) const save: AtomHandle<AsyncResult<any, unknown>>save.AtomHandle<AsyncResult<any, unknown>>.set: (next: AsyncResult<any, unknown>) => voidset(const email: stringemail as never);
}} > <JSX.IntrinsicElements.input: InputHTMLAttributesinput name?: Attrname="email" type?: string | undefinedtype="email" placeholder?: Attrplaceholder="you@company.com" /> <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton type?: string | undefinedtype="submit">Join waitlist</JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton> </JSX.IntrinsicElements.form: FormHTMLAttributesform> ); };

For optimistic updates, use Atom.optimisticFn.

Share fetches

resource() fetches once per key and shares the result. Concurrent mounts dedupe on one request; remounts seed from the cache:

import { const resource: <T>(key: string, fetcher: ResourceFetcher<T>, opts?: ResourceOptions) => Resource<T>
Fetch-once, share-everywhere async state for `key`. Concurrent mounts dedupe on one in-flight request; remounts seed from the shared cache (and revalidate when `staleWhileRevalidate` holds). Call unconditionally at the top level of a sync component, before any `await`.
resource
} from "ilha";
const const User: () => ViewUser = () => { const const user: Resource<any>user = resource<any>(key: string, fetcher: ResourceFetcher<any>, opts?: ResourceOptions): Resource<any>
Fetch-once, share-everywhere async state for `key`. Concurrent mounts dedupe on one in-flight request; remounts seed from the shared cache (and revalidate when `staleWhileRevalidate` holds). Call unconditionally at the top level of a sync component, before any `await`.
resource
("user:42", (key: stringkey) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+3 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
fetch
(`/api/${key: stringkey}`).Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>
Attaches callbacks for the resolution and/or rejection of the Promise.
@paramonfulfilled The callback to execute when the Promise is resolved.@paramonrejected The callback to execute when the Promise is rejected.@returnsA Promise for the completion of which ever callback is executed.
then
((r: Responser) => r: Responser.Body.json(): Promise<any>
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)
json
())
); if (const user: Resource<any>user.
Resource<any>.loading: AtomHandle
() => boolean
loading
()) return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>Loading</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
(const user: Resource<any>user.
Resource<any>.data: AtomHandle
() => any
data
())}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
};
Handle Meaning
data Last value, or undefined
error Last failure, or undefined
loading True while a request is in flight
refetch() Fetch again; joins an in-flight request

invalidate(key) drops the cached value and refetches it in every live component:

import { const invalidate: (key: string) => void
Drop the cached value for `key` and refetch it in live components.
invalidate
} from "ilha";
function invalidate(key: string): void
Drop the cached value for `key` and refetch it in live components.
invalidate
("user:42");

Call resource() unconditionally at the top level, before any await. The fetcher receives an abort signal that fires when a newer fetch starts or the component unmounts. On the server each render fetches fresh — the shared cache never crosses requests. await renderToString(Component) waits for the fetch; pass {user.data} in JSX to put the value in the HTML, since the component body renders once on the server.

Lazy initialization

atom.lazy(() => …) runs once per slot. Use it for expensive init or to store a function value — not atom(fn).

import { const atom: AtomFnatom } from "ilha";

const const Panel: () => ViewPanel = () => {
  const const settings: AtomHandle<any>settings = const atom: AtomFnatom.AtomFn.lazy: <any>(init: () => any) => AtomHandle<any>lazy(() => {
    if (typeof var localStorage: Storage
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
localStorage
=== "undefined") return {};
return var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.parse(text: string, reviver?: (this: any, key: string, value: any) => any): any
Converts a JavaScript Object Notation (JSON) string into an object.
@paramtext A valid JSON string.@paramreviver A function that transforms the results. This function is called for each member of the object. If a member contains nested objects, the nested objects are transformed before the parent object is.@throws{SyntaxError} If `text` is not valid JSON.
parse
(var localStorage: Storage
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
localStorage
.Storage.getItem(key: string): string | null
The **`getItem()`** method of the Storage interface, when passed a key name, will return that key's value, or null if the key does not exist, in the given Storage object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Storage/getItem)
getItem
("prefs") ?? "{}");
}); return <JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>{var JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
JSON
.JSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
stringify
(
const settings: AtomHandle
() => any
settings
())}</JSX.IntrinsicElements.p: HTMLAttributes<HTMLParagraphElement>p>;
};

Props vs state

State initialized from props does not reset when props change. Atoms hold data, not JSX.

Was this page helpful?