Guides

Fetching data

Edit this page

For 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.


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 userId
const 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:

  1. Perform optimistic writes
  2. yield/await async work
  3. 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 store
refresh(todos);
// Thunk form — re-runs an expression
refresh(() => 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.x2.0
createResource(source, fetcher)createMemo(() => fetcher(source()))
resource.loadingisPending(() => memo()) (after initial load)
resource.latestlatest(() => memo())
<Suspense><Loading>
resource.mutate + resource.refetchaction() + refresh() + createOptimistic
startTransition / useTransitionRemoved — built-in transitions + isPending / Loading
Report an issue with this page