Lifecycle & Actions

action

The primitive for mutations: imperative async workflows whose writes span an async gap — optimistic write, server round-trip, reconciling write — where intermediate state must not leak and failure must revert cleanly (pair with createOptimistic / createOptimisticStore).

Navigation-shaped updates do not need an action. A plain setter call is enough: reads pull the async, and downstream async computeds hold their previous values per-node until the new ones are ready (isPending / latest expose the in-flight state). Reach for action only when writes happen after async work, not merely upstream of it.

Framework-level actions (router form actions, server actions) are specializations of this primitive: they are actions in exactly this sense — the same transactional semantics — with form binding, serialization, and submission tracking layered on top. The shared name is deliberate.

Wraps a generator function so each invocation runs as a single transaction that holds every signal/store write between yields. The surrounding UI sees one atomic update when the action completes; nothing is committed until every action in the transaction has returned.

yield is the transaction-safe suspension point: the action waits for a yielded promise and re-enters the transaction before running the code after it. A plain await does not — the runtime has no hook into an async generator's internal await continuations, so code between an await and the next yield runs OUTSIDE the transaction: writes to fresh signals commit immediately, and anything that creates a reader there — until(), latest(), a memo or effect, a mount — is created mainline, where a read of this action's held state makes it born held (A29): staged with the transaction and replayed at its commit. await is still the ergonomic choice for typed results; just put a bare yield before any write or reader creation that follows it — including the expression of the next yield, which is evaluated before the step re-enters:

const saved = await api.createTodo(text); // typed result
yield; // re-enter the transaction before writing or reading
setTodos(t => { ... });
yield until(() => todos.some(t => t.id === saved.id));

(For the same reason, don't call flush() inside an action body — it cannot reveal the held writes and is refused.)

Each call returns a Promise that resolves with the generator's return value, or rejects if it throws. Pair with createOptimistic / createOptimisticStore to apply tentative writes that auto-revert if the action fails.


Import

import { action } from "solid-js";

Type signature

function action<Args extends any[], Y, R>(
genFn: (...args: Args) => Generator<Y, R, any> | AsyncGenerator<Y, R, any>
): (...args: Args) => Promise<R>;

Parameters

genFn

  • Type: (...args: Args) => Generator<Y, R, any> | AsyncGenerator<Y, R, any>

Generator or async generator. Each yield is a suspension point that re-enters the transaction; writes between yields commit together.


Return value

A function with the generator's parameters that returns a promise for its return value. The promise rejects when the generator throws.


Examples

const [todos, setTodos] = createOptimisticStore<Todo[]>([]);
const addTodo = action(async function* (text: string) {
const tempId = crypto.randomUUID();
setTodos((t) => {
t.push({ id: tempId, text, pending: true });
}); // optimistic
const saved = await api.createTodo(text); // network round-trip, typed
yield; // re-enter the transaction
setTodos((t) => {
const i = t.findIndex((x) => x.id === tempId);
if (i >= 0) t[i] = saved;
});
return saved;
});
await addTodo("buy milk");

Caveats

  • A plain await leaves the transaction. Put a bare yield before any writes that follow an await, or use yield in place of await.
  • Do not call flush() inside the generator; it drains the transaction mid-step.
  • Navigation-shaped updates (write an input, read the async result) do not need an action. A plain setter call is held automatically.

Common problems


Learn more

Last updated: 10/15/26, 9:29 AMEdit this pageReport an issue with this page