Reactive utilities

action

Edit this page
import { 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:

  1. Perform optimistic writes with createOptimistic or createOptimisticStore
  2. yield Promises to perform async work
  3. 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
})

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.x
const [data, { mutate, refetch }] = createResource(fetchData)
startTransition(() => {
mutate(optimistic)
})
await saveToServer()
refetch()
// 2.0
const save = action(function* (value) {
setOptimistic(value)
yield saveToServer()
refresh(data)
})
Report an issue with this page