Fetching data
Edit this pageFor most modern web applications, data fetching is a common task.
In Solid 2.0, async is a first-class capability of computations — you can use createMemo or derived stores to fetch data, and the reactive system handles loading, error, and refresh states automatically. For the conceptual foundation, see Async Reactivity.
createResource has been removed. Async data is now handled by standard computations (createMemo, createStore(fn)) that return Promises. Wrap reads of async values in <Loading> to display fallback UI while data is pending.
Async computations
Any computation may return a Promise to represent pending work. Consumers read the accessor as usual; if the value isn't ready yet, the reactive graph suspends until it resolves.
import { createSignal, createMemo, Loading } from "solid-js";
const fetchUser = async (id) => { const response = await fetch(`https://swapi.dev/api/people/${id}/`); return response.json();};
function App() { const [userId, setUserId] = createSignal(1); const user = createMemo(() => fetchUser(userId()));
return ( <div> <input type="number" min="1" placeholder="Enter Numeric Id" onInput={(e) => setUserId(e.currentTarget.value)} /> <Loading fallback={<p>Loading...</p>}> <div>{JSON.stringify(user())}</div> </Loading> </div> );}When userId changes, the memo re-runs the fetch. While the Promise is unresolved, the <Loading> boundary displays its fallback. Once the data arrives, the content renders automatically.
This approach pushes "loading state" into UI structure (boundaries) rather than leaking it into every type with T | undefined.
Loading boundaries
Loading is a component that shows fallback UI while its subtree has unresolved async values.
import { Loading } from "solid-js";
<Loading fallback={<Spinner />}> <UserProfile id={id()} /></Loading>Loading is intended for initial readiness: it handles the first time a subtree reads async values that aren't ready yet. After the subtree has produced a value, subsequent revalidation should generally not show the fallback again — use isPending for that.
You can nest Loading boundaries to control where loading UI appears and avoid blocking large subtrees:
<Loading fallback={<div>Loading page...</div>}> <Header /> <Loading fallback={<div>Loading content...</div>}> <MainContent /> </Loading> <Sidebar /></Loading>Error handling
To handle errors during data fetching, wrap async reads in an Errored boundary:
import { createMemo } from "solid-js";
const user = createMemo(() => fetchUser(userId()));
<Errored fallback={(err, reset) => <span>Error: {err.message}</span>}> <Loading fallback={<p>Loading...</p>}> <div>{JSON.stringify(user())}</div> </Loading></Errored>Stale-while-revalidating with isPending
isPending answers: "Does this expression currently have pending async work while also having a usable stale value?"
isPending is false during the initial Loading fallback (there is no stale value yet). It becomes useful once you've rendered at least once and want to show a subtle "refreshing…" indicator during revalidation without replacing the whole subtree with a spinner.
import { createMemo, isPending, Show } from "solid-js";
const users = createMemo(() => fetchUsers());
function UserList() { return ( <> <Show when={isPending(() => users())}> <div class="refreshing-indicator">Refreshing...</div> </Show> <Loading fallback={<Spinner />}> <List users={users()} /> </Loading> </> );}Peeking at in-flight values with latest
latest(fn) reads the "in-flight" value of a signal or computation during transitions. It may fall back to the stale value if the next value isn't available yet.
import { createSignal, createMemo, latest } from "solid-js";
const [userId, setUserId] = createSignal(1);const user = createMemo(() => fetchUser(userId()));
// During a transition, this can reflect the in-flight userIdconst latestUserId = () => latest(userId);Mutations with action
While async data reads can be modeled as computations, mutations need a different tool. action() wraps a generator or async generator for mutations. Inside an action, you can:
- Perform optimistic writes
- yield/await async work
- Refresh derived async computations via
refresh()
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) { // optimistic write — UI updates immediately setOptimisticTodos((s) => s.list.push(todo));
// perform async work yield api.addTodo(todo);
// refresh derived reads — fresh server data replaces optimistic overlay refresh(todos);});Call it from an event handler:
<button onClick={() => addTodo({ id: 1, text: "New task" })}> Add Task</button>refresh — explicit recomputation
refresh() explicitly re-runs derived reads when you know the underlying source-of-truth may have changed (e.g. after an action completes a server write).
// Refreshable form — re-runs the derived storerefresh(todos);
// Thunk form — re-runs an expressionrefresh(() => query.user(id()));Optimistic updates
Solid 2.0 provides optimistic primitives that behave like signals/stores but reset to their source when the transition completes.
createOptimistic
createOptimistic has the same surface as createSignal, but its writes are treated as optimistic — values revert when the transition completes.
import { createOptimistic, action } from "solid-js";
const [name, setName] = createOptimistic("Alice");
const updateName = action(function* (next) { setName(next); // optimistic — UI updates immediately yield api.saveName(next); // async work});createOptimisticStore
createOptimisticStore is the store analogue. A common pattern is to derive from a source getter and apply optimistic mutations in an action:
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 yield api.addTodo(todo); // async work refresh(todos); // refresh source reads});Migration from Solid 1.x
| 1.x | 2.0 |
|---|---|
createResource(source, fetcher) | createMemo(() => fetcher(source())) |
resource.loading | isPending(() => memo()) (after initial load) |
resource.latest | latest(() => memo()) |
<Suspense> | <Loading> |
resource.mutate + resource.refetch | action() + refresh() + createOptimistic |
startTransition / useTransition | Removed — built-in transitions + isPending / Loading |