Skip to main content

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.

When to use which

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 });
NameTypeRequiredDescription
keyFetchKey✔️The key to uniquely identify the fetched data in the cache. Same semantics as useFetch.
actionDataFunctionType<DATA_TYPE>✔️The data function, or an object containing data / before / after. Same semantics as useFetch.
optionsSuspenseFetchOptionsThe 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 optionForced valueWhy
suspensetrueA suspense fetch always suspends.
errorBoundarytrueErrors are always thrown to the nearest error boundary, never returned as error.
suspendAfterFirstLoadfalseSuspends on the first load only; background refetches/polls update silently without re-suspending.
enabledtrueA 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:

PropertyTypeDescription
dataDATA_TYPEThe resolved data. Guaranteed present (never null).
errorDefaultResponseError | nullAlmost always null — errors are thrown to the error boundary rather than returned.
isLoadingbooleanIndicates a background refetch is in flight (the initial load has already resolved).
isDonebooleanReturns true when the data is finished loading.
isErrorbooleanReturns true when the request got an error.
isPrefetchedbooleanReturns true when the data was prefetched on the server.
isStalebooleanReturns true when the data was retrieved from cache.
resetErrorFunctionManually clear the error state from the cache and local state.
refetchFunctionRe-execute the action.
requestDataRequestThe DataRequest instance.
prefetchFunctionExecutes 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 data is preserved across refetches.
  • error boundary — how thrown errors are surfaced.