useSuspenseFetch
The useSuspenseFetch hook is the Suspense-first companion to useFetch. It always suspends the component until data is available and always throws errors to the nearest error boundary. Because of this, data is guaranteed to be present in the return value, so you can drop the isLoading / isError branches that useFetch requires.
import { useSuspenseFetch } from '@archibald/client';
const RETURN_VALUE = useSuspenseFetch<DATA_TYPE>(PARAMETERS);
It shares the exact same request handling as useFetch — key building and grouping, transferred SSR state, enduring entries, prerender prefetch, polling, focus refetch and before/after events. The same key is fully interchangeable between the two hooks, so one component can read a key with useSuspenseFetch while another reads it with useFetch.
Use useSuspenseFetch for the primary data a component cannot render without — it removes loading/error boilerplate and makes the happy path the only path in your component. Use useFetch when you need to render before data arrives (manual loading states, enabled: false gating, or handling the error inline instead of throwing it).
Requirements
Because the hook always suspends and always throws, the component must be rendered inside:
- a
<Suspense>boundary (for the loading fallback), and - an error boundary (to catch a rejected request).
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<LoadingSpinner />}>
<UserList />
</Suspense>
</ErrorBoundary>
Parameters
useSuspenseFetch accepts the same two calling styles as useFetch:
// Split-args form
useSuspenseFetch<DATA_TYPE>(key, action, options?);
// Single-object form
useSuspenseFetch<DATA_TYPE>({ key, data, before, after, ...options });
| Name | Type | Required | Description |
|---|---|---|---|
| key | FetchKey | ✔️ | The key to uniquely identify the fetched data in the cache. Same semantics as useFetch. |
| action | DataFunctionType<DATA_TYPE> | ✔️ | The data function, or an object containing data / before / after. Same semantics as useFetch. |
| options | SuspenseFetchOptions | The options that define the behavior of the hook. See below. |
Options
- Type:
SuspenseFetchOptions
SuspenseFetchOptions is the FetchOptions type without the switches that control whether the hook suspends. The following options are forced by useSuspenseFetch and cannot be overridden:
| Forced option | Forced value | Why |
|---|---|---|
suspense | true | A suspense fetch always suspends. |
errorBoundary | true | Errors are always thrown to the nearest error boundary, never returned as error. |
suspendAfterFirstLoad | false | Suspends on the first load only; background refetches/polls update silently without re-suspending. |
enabled | true | A suspense fetch is always active — there is no "disabled" state, because data must resolve. |
Every other FetchOptions property is accepted and behaves exactly as it does in useFetch — for example ttl, enduring, poll, refetch, refetchAfterFocus, refetchAfterHydrate, ssr, clearOnRefetch, error, enableOnlyWhenAllKeysTruthy.
Return value
- Type:
SuspenseFetchReturn<DATA_TYPE>
Identical to the useFetch return value, except data is non-nullable — the hook only returns once data has resolved:
| Property | Type | Description |
|---|---|---|
| data | DATA_TYPE | The resolved data. Guaranteed present (never null). |
| error | DefaultResponseError | null | Almost always null — errors are thrown to the error boundary rather than returned. |
| isLoading | boolean | Indicates a background refetch is in flight (the initial load has already resolved). |
| isDone | boolean | Returns true when the data is finished loading. |
| isError | boolean | Returns true when the request got an error. |
| isPrefetched | boolean | Returns true when the data was prefetched on the server. |
| isStale | boolean | Returns true when the data was retrieved from cache. |
| resetError | Function | Manually clear the error state from the cache and local state. |
| refetch | Function | Re-execute the action. |
| request | DataRequest | The DataRequest instance. |
| prefetch | Function | Executes the hook and puts the data in the cache. The data is not returned. |
Example
import { useSuspenseFetch } from '@archibald/client';
function UserList() {
// No `isLoading` / `!data` guard needed — `data` is always here.
const { data } = useSuspenseFetch<User[]>('users', () => actionGetUsers());
return (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// The loading and error UI live in the boundaries, not in the component.
<ErrorBoundary fallback={<UsersError />}>
<Suspense fallback={<LoadingSpinner />}>
<UserList />
</Suspense>
</ErrorBoundary>;
Compare this with the equivalent useFetch code, which has to guard data and handle isLoading before it can render the list.
How suspension works
useSuspenseFetch suspends through the React 19 use() API, which unwraps the cache-stable request promise: it suspends the render while the promise is pending, integrates with the nearest <Suspense> boundary, and re-throws a rejection to the nearest error boundary. On runtimes that don't expose use() (older React, Preact) it falls back to throwing the promise, which suspends the same way. This is the same suspension seam described in the suspense deep dive.
Unlike useFetch — where the mount effect kicks off the request — useSuspenseFetch guarantees an in-flight request during render even on a cold client cache, so there is always a promise to suspend on (parity with React Query's useSuspenseQuery).
See also
- useFetch — the non-suspense-first base hook.
- suspense — how the suspense seam works step by step.
- structural sharing — how referential stability of
datais preserved across refetches. - error boundary — how thrown errors are surfaced.