action
Edit this pageimport { action } from "solid-js"
function action<T extends (...args: any[]) => Generator | AsyncGenerator>( fn: T): (...args: Parameters<T>) => Promise<ReturnType<T>>action() wraps a generator or async generator function for mutations. It returns an async function you can call from event handlers. Each call runs inside a transition — the reactive system coordinates optimistic writes, async work, and data refreshes automatically.
For a conceptual overview of the async model, see Async Reactivity.
Inside an action, you can:
- Perform optimistic writes with
createOptimisticorcreateOptimisticStore - yield Promises to perform async work
- Refresh derived async computations via
refresh()
Basic usage
import { action, refresh } from "solid-js"import { createStore } from "solid-js/store"
const [todos] = createStore(() => api.getTodos(), { list: [] })
const addTodo = action(function* (todo) { yield api.addTodo(todo) // async work refresh(todos) // refresh reads})Call it from an event handler:
<button onClick={() => addTodo({ id: 1, text: "New task" })}> Add Todo</button>With optimistic updates
Combine with createOptimistic or createOptimisticStore for instant UI feedback:
import { action, refresh } from "solid-js"import { createStore, createOptimisticStore, snapshot } from "solid-js/store"
const [todos] = createStore(() => api.getTodos(), { list: [] })const [optimisticTodos, setOptimisticTodos] = createOptimisticStore( () => snapshot(todos), { list: [] })
const addTodo = action(function* (todo) { setOptimisticTodos((s) => s.list.push(todo)) // optimistic — instant UI yield api.addTodo(todo) // async work refresh(todos) // refresh source})Generator forms
Standard generator (function*)
Use yield to wait on Promises. The action pauses at each yield and resumes when the Promise resolves:
const save = action(function* (data) { yield api.save(data) // pauses here until the Promise resolves refresh(source) // runs after save completes})Async generator (async function*)
For TypeScript ergonomics, an async generator form is also available. Use await for async work and a bare yield to re-enter the transition context:
const saveTodo = action(async function* (todo) { setOptimisticTodos((s) => s.list.push(todo)) const res = await api.addTodo(todo) yield // re-enters the transition context after the await refresh(todos) return res})In the async generator form, the bare yield (with no expression) is important — it signals the runtime to resume in the same transition context after the await. Without it, code after await runs outside the transition.
Error handling
Use standard try/catch inside the generator. Optimistic writes revert automatically regardless of success or failure:
const addTodo = action(function* (todo) { setOptimisticTodos((s) => s.list.push(todo)) try { yield api.addTodo(todo) refresh(todos) } catch (err) { console.error("Failed to add todo:", err) // Optimistic writes revert automatically — no manual rollback needed }})If an error is not caught inside the action, it propagates to the caller as a rejected Promise:
<button onClick={async () => { try { await addTodo({ id: 1, text: "New task" }) } catch (err) { showToast("Something went wrong") }}}> Add Todo</button>Transitions and concurrency
Each action() call creates its own transition. Actions coordinate with the reactive system:
isPending()reflects that a transition is in progress.- Optimistic values overlay during the transition and revert when it settles.
- Multiple transitions can be in flight simultaneously — the system entangles related computations automatically.
Replaces manual mutation patterns
In Solid 1.x, mutations required ad-hoc wrappers with startTransition. Actions provide a structured alternative:
// 1.xconst [data, { mutate, refetch }] = createResource(fetchData)startTransition(() => { mutate(optimistic)})await saveToServer()refetch()
// 2.0const save = action(function* (value) { setOptimistic(value) yield saveToServer() refresh(data)})