| name | solidjs-async |
| description | Handles asynchronous data fetching in SolidJS using createResource with Suspense integration. Use when fetching data from APIs, managing loading/error states, or implementing optimistic updates in SolidJS. |
SolidJS Async Patterns
createResource
The primary primitive for async data fetching. Integrates with Suspense and ErrorBoundary.
Without source (fetch once)
import { createResource } from "solid-js";
const [data, { mutate, refetch }] = createResource(async () => {
const res = await fetch("/api/data");
return res.json();
});
With source signal (auto-refetch)
const [userId, setUserId] = createSignal(1);
const [user] = createResource(userId, async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
setUserId(2);
Source signal behavior:
- When source returns
false, null, or undefined — fetcher is NOT called
- When source changes to a truthy value — fetcher is called with that value
- This enables conditional fetching:
const [searchQuery, setSearchQuery] = createSignal("");
const [results] = createResource(
() => searchQuery() || false,
async (query) => {
const res = await fetch(`/api/search?q=${query}`);
return res.json();
}
);
Resource States
A resource has 5 states:
| State | resource() | .loading | .error | .latest | Description |
|---|
unresolved | undefined | false | undefined | undefined | Initial, fetcher not yet called |
pending | undefined | true | undefined | undefined | Fetching in progress |
ready | T | false | undefined | T | Successfully fetched |
refreshing | T | true | undefined | T | Re-fetching, previous value available |
errored | undefined | false | any | undefined | Fetch failed |
Accessing resource data
const value = data();
const isLoading = data.loading;
const error = data.error;
const latest = data.latest;
const state = data.state;
resource.latest
latest keeps the previous successful value while refreshing. Useful for showing stale data during reload:
function UserList() {
const [users, { refetch }] = createResource(fetchUsers);
return (
<div>
<button onClick={refetch} disabled={users.loading}>
{users.loading ? "Refreshing..." : "Refresh"}
</button>
{/* Show previous data while refreshing instead of spinner */}
<For each={users.latest ?? []}>
{(user) => <div>{user.name}</div>}
</For>
</div>
);
}
mutate and refetch
const [todos, { mutate, refetch }] = createResource(fetchTodos);
mutate(prev => [...(prev ?? []), newTodo]);
await refetch();
await refetch("forced");
Suspense Integration
createResource automatically triggers the nearest <Suspense> boundary when read.
import { createResource, Suspense } from "solid-js";
function UserProfile() {
const [user] = createResource(fetchCurrentUser);
return (
<Suspense fallback={<LoadingSpinner />}>
<div>
<h1>{user()?.name}</h1>
<p>{user()?.email}</p>
</div>
</Suspense>
);
}
Best practice: Use optional chaining (user()?.name) inside Suspense — the resource starts as undefined and the DOM is pre-created before the resource resolves.
Nested Suspense
Each resource triggers only its closest Suspense boundary:
<Suspense fallback={<PageLoader />}>
<h1>{pageTitle()}</h1>
<Suspense fallback={<SidebarLoader />}>
<Sidebar data={sidebarData()} />
</Suspense>
</Suspense>
Error Handling
Use <ErrorBoundary> to catch resource errors:
import { ErrorBoundary, Suspense } from "solid-js";
<ErrorBoundary
fallback={(err, reset) => (
<div>
<p>Failed to load: {err.message}</p>
<button onClick={reset}>Retry</button>
</div>
)}
>
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
</ErrorBoundary>
You can also check resource.error directly:
const [data] = createResource(fetcher);
return (
<Show when={!data.error} fallback={<div>Error: {data.error?.message}</div>}>
<div>{data()?.value}</div>
</Show>
);
Options
const [data] = createResource(fetcher, {
initialValue: [],
ssrLoadFrom: "server",
deferStream: false,
storage: createSignal,
});
initialValue
When provided, resource() is never undefined and the type reflects this:
const [todos] = createResource(fetchTodos, { initialValue: [] });
return <For each={todos()}>{(todo) => <div>{todo.title}</div>}</For>;
createResource vs createSignal + onMount
const [data] = createResource(fetchData);
const [data, setData] = createSignal<Data>();
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal<Error>();
onMount(async () => {
setLoading(true);
try {
setData(await fetchData());
} catch (e) {
setError(e as Error);
} finally {
setLoading(false);
}
});
Use createResource when:
- You want Suspense integration
- You need loading/error state tracking
- You want auto-refetch on source signal changes
- You want optimistic updates via
mutate
Use manual fetch when:
- You explicitly don't want Suspense to trigger
- You need full control over the fetch lifecycle
- The fetch is a one-time side effect, not data loading